{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreic6yltyq6mqmg3pzzj6mb7smiqqkkw2dtysfwtvi3ryye5m3sietq",
"uri": "at://did:plc:73fkxipzpyu3ph253ldsjzqh/app.bsky.feed.post/3mmeiid3if5c2"
},
"description": "I’m currently on paternity leave, have recently moved into a new house, and have been reading too many articles and watching too many videos in the style of “level up your command line”. This has resulted in me ditching bash in favor of zsh. I’ve used bash since I first started out on RedHat 6.2—or was it even before then? In any case, that’s more than 26 years!\n\nAnother project I’ve started is to self-host more things. Among other things, I’ve discovered ntfy.sh, tried and discarded Dozzle and ",
"path": "/grall-my-small-git-helper-for-multiple-remotes/",
"publishedAt": "2026-05-21T13:08:36.000Z",
"site": "https://robertjacobsen.no",
"tags": [
"bash",
"zsh",
"ntfy.sh",
"Dozzle",
"Beszel",
"Forgejo",
"GitHub",
"aliases documented"
],
"textContent": "I’m currently on paternity leave, have recently moved into a new house, and have been reading too many articles and watching too many videos in the style of “level up your command line”. This has resulted in me ditching bash in favor of zsh. I’ve used bash since I first started out on RedHat 6.2—or was it even before then? In any case, that’s more than 26 years!\n\nAnother project I’ve started is to self-host more things. Among other things, I’ve discovered ntfy.sh, tried and discarded Dozzle and Beszel (both great, but didn’t fit my needs), and set up Forgejo for hosting my code repos alongside GitHub. However, there’s now an inconvenience: keeping the different repositories in sync.\n\n## The inconvenience\n\nI have two remotes, and have them added as follows:\n\n * `origin` (`https://github.com/robertjacobsen/.dotfiles.git`)\n * `forgejo` (`https://git.pilze.no/robert/.dotfiles.git`)\n\n\n\nThe typical workflow for keeping them in sync is to:\n\n\n git remote add forgejo https://git.pilze.no/robert/.dotfiles.git\n git push # the same as “git push origin main” with default upstream\n git push forgejo main\n\nIt’s also worth mentioning that you now have to do `git remote update` to pull all remotes, instead of merely `git fetch` or `git pull`, which would typically pull only one of them.\n\nI am fond of shorthands for my commands. To reload zsh, I have `zr` (aliased to `exec zsh`). The `git remote update` command above: `gru`. Doing a `git merge --ff-only`: `gff`. Doing a `git remote update` and then a `git merge --ff-only`: `gruff`. I have all my aliases documented (some more used than others, I really need to clean that up).\n\n## The solution\n\nGit has a way of adding multiple targets to be pushed with one `git push`. This allows you to add other targets to remotes (such as `origin`), e.g., `git remote set-url --add --push origin https://git.pilze.no/robert/.dotfiles.git`. However, that means that I now have a bloated `origin` that points to multiple things. I don’t like it.\n\nInstead, I added a new remote called `all` which does this:\n\n\n git remote add all https://github.com/robertjacobsen/.dotfiles.git\n git remote set-url --add --push all https://github.com/robertjacobsen/.dotfiles.git\n git remote set-url --add --push all https://git.pilze.no/robert/.dotfiles.git\n\nNow that’s a mouthful to remember. Building on top of the shorthands, I added the function `grall` (from “`git remote add all […]`”). This allows me to add this with ease. I didn’t want to remember the long names, and I assume that the repository name is the same across different remotes.\n\nI set this in my `~/.zshrc.local` file (which is sourced from my `~/.zshrc`):\n\n\n export GITHUB_USER=https://github.com/robertjacobsen\n export FORGEJO_USER=https://git.pilze.no/robert\n\nAnd then have this script added to something that `.zshrc` sources. I have this as part of an `aliases.zsh` file:\n\n\n grall() {\n local repo=\"${1:-$(basename \"$PWD\")}\"\n repo=\"${repo%.git}\"\n\n if [ -z \"$GITHUB_USER\" ] || [ -z \"$FORGEJO_USER\" ]; then\n echo \"GITHUB_USER and FORGEJO_USER must be set (in ~/.zshrc.local)\" >&2\n return 1\n fi\n\n if git remote get-url all >/dev/null 2>&1; then\n echo \"remote 'all' already exists; remove it first if you want to recreate\" >&2\n return 1\n fi\n\n local github_url=\"$GITHUB_USER/$repo.git\"\n local forgejo_url=\"$FORGEJO_USER/$repo.git\"\n\n git remote add all \"$github_url\"\n git remote set-url --add --push all \"$github_url\"\n git remote set-url --add --push all \"$forgejo_url\"\n }\n\nAfter `zsh` is reloaded (or we’ve run `zr`), this can then be used like this:\n\n\n $ grall .dotfiles.git\n\n`grall` then adds the remotes to `all`, which can be verified through `git remote -v`:\n\n\n $ git remote -v\n all https://github.com/robertjacobsen/.dotfiles.git (fetch)\n all https://github.com/robertjacobsen/.dotfiles.git (push)\n all https://git.pilze.no/robert/.dotfiles.git (push)\n forgejo https://git.pilze.no/robert/.dotfiles.git (fetch)\n forgejo https://git.pilze.no/robert/.dotfiles.git (push)\n origin https://github.com/robertjacobsen/.dotfiles.git (fetch)\n origin https://github.com/robertjacobsen/.dotfiles.git (push)\n\nThe last thing we need to do is to set `all/main` as upstream:\n\n\n $ git push -u all main\n\nThis allows subsequent `git push`es to primarily use the `all` remote for main instead of `origin/main`, which in turn means that both `origin` and `forgejo` will be updated. Neat!",
"title": "grall, my small git helper for multiple remotes",
"updatedAt": "2026-05-21T13:08:36.893Z"
}