Skip to content

Instantly share code, notes, and snippets.

@philpennock
Last active January 5, 2023 22:38
Show Gist options
  • Select an option

  • Save philpennock/04c22c0e2bc74f87fb651b61c39eef6f to your computer and use it in GitHub Desktop.

Select an option

Save philpennock/04c22c0e2bc74f87fb651b61c39eef6f to your computer and use it in GitHub Desktop.

Revisions

  1. philpennock revised this gist Apr 18, 2022. 1 changed file with 84 additions and 17 deletions.
    101 changes: 84 additions & 17 deletions git-pb
    Original file line number Diff line number Diff line change
    @@ -36,21 +36,17 @@ get_version_information() {
    cd_to_toplevel
    get_version_information

    # Should this respect core.hooksPath ?
    HOOKS_DIR="$(git rev-parse --git-dir)/hooks"
    readonly HOOKS_DIR

    found_check=false
    for checker in "$HOOKS_DIR/pdp.prepush"; do
    if [ -x "$checker" ]; then
    found_check=true
    stderr "invoking checker: $checker"
    "$checker"
    fi
    done
    if $found_check; then stderr "checkers complete"; fi
    # Remove all GIT_PDP_* variables, don't inherit any.
    # Provide a cleaner env for our hooks.
    unset "${!GIT_PDP_@}"

    should_force=true
    should_set_upstream=false
    should_set_upstream=0
    should_find_upstream=0
    is_trunk=false

    disable_force_because_trunk() {
    @@ -75,37 +71,66 @@ if [[ -z "${FORCE_TRUNK:-}" ]]; then
    fi
    fi

    if ! upstream="$(git for-each-ref --format='%(upstream:remotename)' "$full_branch")" || [ -z "$upstream" ]
    if ! upstream="$(git for-each-ref --format='%(upstream:remotename)' "$full_branch")" || [[ -z "$upstream" ]]
    then
    stderr "missing a current upstream"
    should_set_upstream=true
    should_set_upstream=1
    should_find_upstream=1
    if "$is_trunk"; then
    die "this is a trunk branch, not hunting for an upstream"
    fi
    fi

    if $should_set_upstream && [[ -z "$upstream" ]]; then
    bad_upstream=''
    if [[ -n "$upstream" ]]; then
    pu="$(git config --local --get "remote.$upstream.pushurl" || true )"
    need_new_up=0
    case "${pu,,}" in
    (- | -- | nonexistant | non-existant | nonexistent | non-existent | no | none | off | 0 | disable | disabled | readonly | read-only | ro)
    need_new_up=1
    ;;
    esac
    if (( need_new_up )); then
    stderr "upstream ${upstream@Q} unacceptable for push [${pu@Q}]"
    bad_upstream="$upstream"
    upstream=''
    # Do *NOT* _set_ upstream, just find it.
    # Upstream should be left on the repo which is read-only to us
    should_set_upstream=0
    should_find_upstream=1
    fi
    unset pu need_new_up
    fi

    if (( should_find_upstream )) && [[ -z "$upstream" ]]; then
    if candidate="$(git config --local --get remotes.push)"; then
    stderr "picking upstream '${candidate}' because is remotes.push"
    stderr "picking upstream ${candidate@Q} because is remotes.push"
    upstream="$candidate"
    fi
    fi

    if $should_set_upstream && [[ -z "$upstream" ]]; then
    if (( should_find_upstream )) && [[ -z "$upstream" ]]; then
    for B in main master; do
    candidate="$(git for-each-ref --format='%(upstream:remotename)' "refs/heads/$B")"
    [ "$candidate" != "" ] || continue
    stderr "picking upstream '${candidate}' as per branch '${B}'"
    stderr "picking upstream ${candidate@Q} as per branch ${B@Q}"
    upstream="$candidate"
    break
    done
    fi

    if [[ -n "$bad_upstream" ]] && [[ "$upstream" == "$bad_upstream" ]]; then
    stderr "oops, re-picked ${upstream@Q} after rejecting it"
    die "if repo declares our upstream as invalid to push to, repo should set 'remotes.push' too"
    fi

    # This is why we went to bash: when 2 conditionals & 4 patterns, was willing to stick to sh.
    # With three conditionals and 8 invocation patterns, time to use an array.
    declare -a subcmd=('push')
    if $should_set_upstream; then
    if (( should_set_upstream )); then
    subcmd+=( -u "$upstream" "$branch" )
    elif (( should_find_upstream )); then
    subcmd+=( "$upstream" "$branch" )
    fi
    if $should_force; then
    # this checks that our ref for the remote matches
    @@ -119,4 +144,46 @@ if $should_force; then
    fi
    fi

    # Side-effect: sets cleanliness information
    set_hook_expensive() {
    if [[ -n "${GIT_PDP_DIRTY:-}" ]] || [[ -n "${GIT_PDP_CLEAN:-}" ]]; then
    return 0
    fi
    local status
    status="$(git status --porcelain=v2)"
    if [[ "$status" == "" ]]; then
    declare -gxr GIT_PDP_CLEAN=true
    else
    declare -gxr GIT_PDP_DIRTY=true
    fi
    }

    # For the hooks
    run_checker() {
    local check_cmd="${1:?need a command to run}"
    declare -xr GIT_PDP_BRANCH="$branch"
    declare -xr GIT_PDP_UPSTREAM="$upstream"
    if (( should_set_upstream )); then
    declare -xr GIT_PDP_SET_UPSTREAM=true
    else
    declare -xr GIT_PDP_SET_UPSTREAM=false
    fi
    declare -xr GIT_PDP_FORCE_PUSH=$should_force
    set_hook_expensive
    "$check_cmd"
    }

    # TBD: should I set up anything like an easy way to get exactly the tree being pushed?
    # At this time, the tree visible to the hook is the current working tree, which might be bogus.
    # Or perhaps missing, if bare.
    found_check=false
    for checker in "$HOOKS_DIR/pdp.prepush"; do
    if [ -x "$checker" ]; then
    found_check=true
    stderr "invoking checker: $checker"
    run_checker "$checker"
    fi
    done
    if $found_check; then stderr "checkers complete"; fi

    git "${subcmd[@]}" "$@"
  2. philpennock created this gist Oct 5, 2021.
    122 changes: 122 additions & 0 deletions git-pb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,122 @@
    #!/usr/bin/env bash
    set -euo pipefail
    #
    # git pb: push branch
    # implicit: force with lease, set upstream if needed, etc
    #

    # Path coercion for platforms where git might be in multiple places and I can't
    # mess with the ordering "normally" but want to explicitly pick up newer git
    # here.
    [ -d /opt/local/bin ] && PATH="/opt/local/bin:$PATH"
    [ -d /opt/git/bin ] && PATH="/opt/git/bin:$PATH"

    # shellcheck disable=SC2034
    SUBDIRECTORY_OK=true
    set +eu
    # shellcheck source=/dev/null
    . "$(git --exec-path)/git-sh-setup"
    set -eu

    # I like prefices in front of messages, so we also stomp of the normal git die()
    progname="$(basename "$0" .sh)"
    stderr() { printf >&2 '%s: %s\n' "$progname" "$*"; }
    die() { stderr "$@"; exit 1; }

    get_version_information() {
    local v="$( git --version )"
    v="${v#git version }"
    v="${v%% *}"
    git_version_full="$v"
    git_version_major="${v%%.*}"
    v="${v#*.}"
    git_version_minor="${v%%.*}"
    }

    cd_to_toplevel
    get_version_information

    HOOKS_DIR="$(git rev-parse --git-dir)/hooks"
    readonly HOOKS_DIR

    found_check=false
    for checker in "$HOOKS_DIR/pdp.prepush"; do
    if [ -x "$checker" ]; then
    found_check=true
    stderr "invoking checker: $checker"
    "$checker"
    fi
    done
    if $found_check; then stderr "checkers complete"; fi

    should_force=true
    should_set_upstream=false
    is_trunk=false

    disable_force_because_trunk() {
    stderr "force disabled for branch: ${1:?need a branch name}"
    should_force=false
    is_trunk=true
    }

    full_branch="$(git symbolic-ref HEAD)" || die "not on a branch, not pushing"

    branch="${full_branch#refs/heads/}"
    if [[ -z "${FORCE_TRUNK:-}" ]]; then
    case "$branch" in
    main | master | dev | v? ) disable_force_because_trunk "$branch" ;;
    esac
    if $should_force && protected_s="$(git config --local --get pdp.protect-branches)"; then
    for b in $protected_s; do
    [[ "$branch" == "$b" ]] || continue
    disable_force_because_trunk "$branch"
    break
    done; unset b
    fi
    fi

    if ! upstream="$(git for-each-ref --format='%(upstream:remotename)' "$full_branch")" || [ -z "$upstream" ]
    then
    stderr "missing a current upstream"
    should_set_upstream=true
    if "$is_trunk"; then
    die "this is a trunk branch, not hunting for an upstream"
    fi
    fi

    if $should_set_upstream && [[ -z "$upstream" ]]; then
    if candidate="$(git config --local --get remotes.push)"; then
    stderr "picking upstream '${candidate}' because is remotes.push"
    upstream="$candidate"
    fi
    fi

    if $should_set_upstream && [[ -z "$upstream" ]]; then
    for B in main master; do
    candidate="$(git for-each-ref --format='%(upstream:remotename)' "refs/heads/$B")"
    [ "$candidate" != "" ] || continue
    stderr "picking upstream '${candidate}' as per branch '${B}'"
    upstream="$candidate"
    break
    done
    fi

    # This is why we went to bash: when 2 conditionals & 4 patterns, was willing to stick to sh.
    # With three conditionals and 8 invocation patterns, time to use an array.
    declare -a subcmd=('push')
    if $should_set_upstream; then
    subcmd+=( -u "$upstream" "$branch" )
    fi
    if $should_force; then
    # this checks that our ref for the remote matches
    subcmd+=( --force-with-lease )
    if [[ $git_version_major -gt 2 ]] || [[ $git_version_major -eq 2 && $git_version_minor -ge 30 ]]; then
    # this guards against something else doing `git remote update`
    # and having matched our ref for the remote, by requiring that
    # the ref for the remote be reachable from a reflog entry for
    # the current branch.
    subcmd+=( --force-if-includes )
    fi
    fi

    git "${subcmd[@]}" "$@"