Push a folder of scripts or code to GitHub with one command. Pure Python — no git installation required, works anywhere Python runs (including iOS shells like a-Shell).
python gitpush.py push ./myscriptsThat single command will:
- Turn the folder into a git repo (if it isn't one yet)
- Detect every new, changed, and deleted file
- Skip junk (
__pycache__,.venv,node_modules,.pyc,.DS_Store, editor folders...) - Commit everything
- Create the GitHub repo if it doesn't exist (named after the folder)
- Push
pip install dulwich- Go to github.com → Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token
- Repository access: All repositories (or select specific ones)
- Permissions:
- Contents: Read and write — needed to push
- Administration: Read and write — needed to auto-create repos
- Copy the token (
github_pat_...)
export GITHUB_TOKEN=github_pat_yourtoken
export GITHUB_USER=yourusername
export GITHUB_EMAIL=you@example.com # optional, see below
export GITPUSH_ROOT=~/Documents/projects # optional safety fence, see belowAll are optional — if token or username is missing, gitpush simply asks you when it needs them. Setting them just skips the prompts.
GITHUB_EMAIL controls the author email on your commits. If unset, gitpush derives your GitHub noreply address from your username automatically — commits still link to your profile and count on your contribution graph, without exposing a real email.
First push of a new repo interactively asks for optional extras — description, website, topics — press Enter to skip any of them.
GITPUSH_ROOT is a safety fence: when set, gitpush refuses to push or pushall any folder outside that directory. Useful to guarantee you can never accidentally push your Downloads folder.
Three layers keep junk out of your repos:
- Built-in filters —
__pycache__,.venv,node_modules,.pyc,.DS_Store, editor folders are always skipped (editSKIP_PARTSetc. at the top of the script to change). .gitignore— Dulwich honors standard.gitignorefiles, same as regular git..gitpushignore(or_gitpush_ignore.txt— handy on iOS, where dotfiles are awkward) — auto-created with a commented template on your first push or pushall in a folder, so you always have somewhere to add exclusions. One pattern per line,#for comments, wildcards allowed. The ignore file itself is never pushed to GitHub:- In a project folder: those files/folders are never staged by
push. - In a parent folder:
pushallskips those subfolders entirely.
- In a project folder: those files/folders are never staged by
# _gitpush_ignore.txt in ~/Documents
models
experiments
*.bin
pushall also lists every folder → repo mapping and asks for confirmation before touching anything (-y skips the prompt for scripting).
| Command | What it does |
|---|---|
push FOLDER |
The main one: commit everything + push. Creates the GitHub repo if needed. |
status FOLDER |
List what would be pushed, change nothing |
log FOLDER |
Show recent commits |
pushall FOLDER |
Push every subfolder to its own repo in one go (same flags as push) |
pull FOLDER or pull user/name |
Fetch + merge the latest from GitHub (clones if you don't have it yet) |
clone user/name |
Copy a GitHub repo down |
create NAME |
Create an empty repo on GitHub (rarely needed — push does it) |
init FOLDER |
Make a folder a git repo without pushing (rarely needed — push does it) |
| Flag | Works on | Meaning | Default |
|---|---|---|---|
-r, --repo |
push, pull, clone | Target repo. name or user/name |
$GITHUB_USER/foldername |
-m, --message |
push | Commit message | made some changes |
-n, --dry-run |
push | Preview only, touch nothing | off |
-p, --private |
push, create | Make auto-created repo private | public |
-f, --force |
push, pushall | Force push - overwrite remote history (use after rewriting local history) | off |
-y, --yes |
pushall | Skip the are-you-sure confirmation | off |
-b, --branch |
push, pull | Branch to use | main |
-t, --token |
all remote commands | Token override | $GITHUB_TOKEN or prompt |
-c, --count |
log | How many commits to show | 10 |
# Push a folder - repo "myscripts" is created automatically if missing
python gitpush.py push ./myscripts
# Same, but with a proper commit message
python gitpush.py push ./myscripts -m "add backup script"
# Push to a repo with a different name than the folder
python gitpush.py push ./myscripts -r utilities
python gitpush.py push ./myscripts -r someoneelse/shared-repo
# Auto-create as a PRIVATE repo on first push
python gitpush.py push ./secrets-lab -p
# See what would be pushed without doing anything
python gitpush.py push ./myscripts -n
# Check for unpushed changes / recent history
python gitpush.py status ./myscripts
python gitpush.py log ./myscripts -c 5
# Get a repo onto a new device
python gitpush.py clone yourname/myscripts
python gitpush.py clone myscripts # your own repos: name alone is enough
# Pull changes made elsewhere - by folder or by repo name
python gitpush.py pull ./myscripts
python gitpush.py pull yourname/myscripts # clones automatically if not presentpython gitpush.py pushall ~/Documents/projects
python gitpush.py pushall ~/Documents/projects -n # preview first
# hack on your scripts...
python gitpush.py push ./myscripts # done. that's the whole workflow."Repo X already exists but your token can't see it" — fine-grained token without that repo under Repository access. Edit the token on GitHub and add it.
GitHub refused to create the repo (401/403/404) — token is missing Administration: write, or is expired.
pull fails on a brand-new repo — the branch must exist on GitHub first. Push once, then pull works.
Where does the token go? — only into memory for the API call and push URL. Never written to disk.
Something's junk-filtered that you actually want — edit SKIP_PARTS / SKIP_NAMES / SKIP_SUFFIXES at the top of gitpush.py.
Works with classic tokens too (ghp_... with repo scope) — everything behaves the same.