Created
August 26, 2026 09:20
-
-
Save lzap/bfb17aeaab636f89232e8c993819a177 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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