Skip to content

Instantly share code, notes, and snippets.

@lzap
Created August 26, 2026 09:20
Show Gist options
  • Select an option

  • Save lzap/bfb17aeaab636f89232e8c993819a177 to your computer and use it in GitHub Desktop.

Select an option

Save lzap/bfb17aeaab636f89232e8c993819a177 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
#
# git-configure: set up a repo the way I like it.
#
# 1. If the repo lives on github.com, make sure the remotes follow my
# convention:
# upstream -> the canonical project
# origin -> git@github.com:lzap/<repo>.git (my fork; created via gh
# if it does not exist yet)
# 2. Rename a local "develop" or "master" branch to "main" and point it at
# the upstream "main" branch for tracking.
#
set -euo pipefail
FORK_OWNER="lzap"
UPSTREAM_REMOTE="upstream"
NEW_BRANCH="main"
die() { echo "git-configure: $*" >&2; exit 1; }
# Must be inside a git work tree.
git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|| die "not inside a git repository"
# --- helpers ---------------------------------------------------------------
have_remote() { git remote get-url "$1" >/dev/null 2>&1; }
# Echo "owner/repo" for a github.com URL, else return non-zero.
parse_gh_slug() {
local url="$1" slug
[ -n "$url" ] || return 1
case "$url" in
*github.com*) ;;
*) return 1 ;;
esac
slug="${url#*github.com}" # drop everything up to and including github.com
slug="${slug#[:/]}" # drop the leading ':' or '/'
slug="${slug%.git}" # drop a trailing '.git'
slug="${slug%/}" # drop a trailing '/'
[ -n "$slug" ] || return 1
echo "$slug"
}
remote_slug() { parse_gh_slug "$(git remote get-url "$1" 2>/dev/null)"; }
# --- github remote setup ---------------------------------------------------
#
# Returns non-zero (skip) if the repo is not hosted on github.com.
setup_github_remotes() {
local upstream_slug=""
if have_remote "$UPSTREAM_REMOTE"; then
# Upstream already present. Only proceed if it's a github remote.
upstream_slug="$(remote_slug "$UPSTREAM_REMOTE" || true)"
[ -n "$upstream_slug" ] || return 1
elif have_remote origin; then
local origin_slug origin_owner
origin_slug="$(remote_slug origin || true)"
[ -n "$origin_slug" ] || return 1 # origin isn't github -> not our case
origin_owner="${origin_slug%%/*}"
if [ "$origin_owner" = "$FORK_OWNER" ]; then
# origin is my fork but there's no upstream yet; discover the parent.
local parent
parent="$(gh repo view "$origin_slug" --json parent \
--jq 'if .parent then .parent.owner.login + "/" + .parent.name else empty end' \
2>/dev/null || true)"
[ -n "$parent" ] \
|| die "origin is a fork but its upstream parent could not be determined"
echo "git-configure: adding $UPSTREAM_REMOTE -> $parent"
git remote add "$UPSTREAM_REMOTE" "https://github.com/$parent.git"
upstream_slug="$parent"
else
# Fresh clone: origin points at the canonical repo. Promote it.
echo "git-configure: renaming remote origin -> $UPSTREAM_REMOTE"
git remote rename origin "$UPSTREAM_REMOTE"
upstream_slug="$origin_slug"
fi
else
return 1 # no usable remotes
fi
echo "git-configure: upstream is $upstream_slug"
# Ensure my fork exists and origin points at it over SSH.
local repo fork_slug fork_url
repo="${upstream_slug##*/}"
fork_slug="$FORK_OWNER/$repo"
fork_url="git@github.com:$fork_slug.git"
if ! gh repo view "$fork_slug" >/dev/null 2>&1; then
echo "git-configure: fork $fork_slug not found; creating it with gh"
# With an explicit repo argument gh only creates the fork; it does not
# clone or add a remote (the --clone/--remote flags are rejected here).
gh repo fork "$upstream_slug"
# Forking can be asynchronous; wait for it to become visible.
local _
for _ in $(seq 1 10); do
gh repo view "$fork_slug" >/dev/null 2>&1 && break
sleep 1
done
gh repo view "$fork_slug" >/dev/null 2>&1 \
|| die "fork $fork_slug did not become available"
fi
if have_remote origin; then
local cur; cur="$(git remote get-url origin)"
if [ "$cur" != "$fork_url" ]; then
echo "git-configure: pointing origin -> $fork_url"
git remote set-url origin "$fork_url"
else
echo "git-configure: origin already points at $fork_url"
fi
else
echo "git-configure: adding origin -> $fork_url"
git remote add origin "$fork_url"
fi
git fetch --quiet origin || true
}
if setup_github_remotes; then
:
else
echo "git-configure: not a github.com repo; skipping remote configuration"
fi
# --- rename branch to main -------------------------------------------------
git remote get-url "$UPSTREAM_REMOTE" >/dev/null 2>&1 \
|| die "remote '$UPSTREAM_REMOTE' not found"
# Find the old branch: prefer "develop", then "master".
OLD_BRANCH=""
for candidate in develop master; do
if git show-ref --verify --quiet "refs/heads/$candidate"; then
OLD_BRANCH="$candidate"
break
fi
done
if [ -z "$OLD_BRANCH" ]; then
echo "git-configure: no local 'develop' or 'master' branch to rename"
else
# Bail out if a local "main" already exists.
if git show-ref --verify --quiet "refs/heads/$NEW_BRANCH"; then
die "local branch '$NEW_BRANCH' already exists"
fi
echo "git-configure: renaming '$OLD_BRANCH' -> '$NEW_BRANCH'"
git branch -m "$OLD_BRANCH" "$NEW_BRANCH"
echo "git-configure: fetching '$UPSTREAM_REMOTE'"
git fetch --quiet "$UPSTREAM_REMOTE"
# Prefer tracking upstream/main; fall back to the original name if upstream
# hasn't been renamed yet.
TRACK_BRANCH=""
for candidate in "$NEW_BRANCH" "$OLD_BRANCH"; do
if git show-ref --verify --quiet "refs/remotes/$UPSTREAM_REMOTE/$candidate"; then
TRACK_BRANCH="$candidate"
break
fi
done
[ -n "$TRACK_BRANCH" ] \
|| die "no '$NEW_BRANCH' or '$OLD_BRANCH' branch found on '$UPSTREAM_REMOTE'"
echo "git-configure: tracking '$UPSTREAM_REMOTE/$TRACK_BRANCH'"
# --set-upstream-to reverse-maps the tracking ref through the remote's fetch
# refspec and refuses ("not a branch") if the remote has a non-standard
# refspec. Fall back to writing the branch config directly in that case.
if ! git branch --set-upstream-to="$UPSTREAM_REMOTE/$TRACK_BRANCH" "$NEW_BRANCH" 2>/dev/null; then
git config "branch.$NEW_BRANCH.remote" "$UPSTREAM_REMOTE"
git config "branch.$NEW_BRANCH.merge" "refs/heads/$TRACK_BRANCH"
fi
fi
echo "git-configure: done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment