Skip to content

Instantly share code, notes, and snippets.

@x-yuri
Last active December 14, 2024 21:07
Show Gist options
  • Save x-yuri/0ac53cb7999b3b0352c1ec0ac8f5f4cc to your computer and use it in GitHub Desktop.
Save x-yuri/0ac53cb7999b3b0352c1ec0ac8f5f4cc to your computer and use it in GitHub Desktop.
git: push.default = matching

git: push.default = matching

Obsoleted by https://gist.github.com/x-yuri/a25ae25574e4b7e3b0f37cbed04e2035.

In this mode git push pushes matching branches, i.e. all local branches that exist in a remote, to their remote counterparts (if there's a local branch ba and a remote branch origin/ba, it pushes ba to origin/ba):

  • First it chooses a remote:
    • if the current branch has an upstream, it chooses the remote the upstream points to
    • else if there's only one remote, it chooses this only remote
    • else it chooses origin if it exists
    • otherwise it fails
  • Then it pushes matching branches to the remote.
  • If the upstream of the current branch is in the local repo, there can be no matching branches by definition (there can't be 2 branches with the same name in a repository).
  • It never chooses a non-matching branch, even if it's an upstream. If ba has an upstream origin/ba2 and there's origin/ba, it will choose origin/ba.

a.bats:

strict() { set -euo pipefail; shopt -s inherit_errexit; "$@"; }

setup() {
    [ "$BATS_LIB_PATH" = /usr/lib/bats ] && BATS_LIB_PATH=$HOME/.bats/lib:$BATS_LIB_PATH
    bats_load_library bats-support
    bats_load_library bats-assert
    strict
}

# the state of the cloned repository (1 commit, 1 branch ba, 3 remotes origin, origin2, origin3):
# a (HEAD -> ba, origin/ba, origin2/ba, origin3/ba)

# if there's an upstream:

# a (origin/ba, origin2/ba, origin3/ba) --- b (ba), ba -> origin2/ba => ba -> origin2/ba
@test "pushes when there's a matching upstream branch" {
    mk_cloned_repo
    git config push.default matching
    git branch -u origin2/ba
    git commit --allow-empty -m b

    git push

    assert_equal_refs origin/ba  ba~
    assert_equal_refs origin2/ba ba
    assert_equal_refs origin3/ba ba~
}

# -> it uses the upstream to choose the remote

# a (origin/ba, origin2/ba, origin3/ba, origin/ba2) --- b (ba), ba -> origin/ba2 => ba -> origin/ba
@test "pushes to the matching branch, even though there's a non-matching upstream" {
    mk_cloned_repo
    git config push.default matching
    git push origin ba:ba2
    git branch -u origin/ba2
    git commit --allow-empty -m b

    git push

    assert_equal_refs origin/ba  ba
    assert_equal_refs origin2/ba ba~
    assert_equal_refs origin3/ba ba~
    assert_equal_refs origin/ba2 ba~
}

# -> it can't choose a non-matching branch by definition, even if it's an upstream
# as such it uses the upstream only to choose the remote

# a (origin/ba, origin2/ba, origin3/ba, bb) --- b (ba), ba -> bb => -
@test "doesn't push when there're matching non-upstream branches, but the upstream is in the local repo" {
    mk_cloned_repo
    git config push.default matching
    git branch bb
    git branch -u bb
    git commit --allow-empty -m b

    git push

    assert_equal_refs origin/ba  ba~
    assert_equal_refs origin2/ba ba~
    assert_equal_refs origin3/ba ba~
    assert_equal_refs bb         ba~
}

# -> it chooses the local repo as a remote, but no matching branches can exist there

# if there's no upstream:

# a (origin2/ba) --- b (ba) => ba -> origin2/ba
@test "pushes when there's a matching non-upstream branch and there are no other remotes" {
    mk_cloned_repo
    git config push.default matching
    git branch --unset-upstream
    git commit --allow-empty -m b
    git remote rm origin
    git remote rm origin3

    git push

    assert_equal_refs origin2/ba ba
}

# -> if there's only one remote that's the one it chooses

# when there're more than 1 remote:

# a (origin/ba, origin2/ba, origin3/ba) --- b (ba) => ba -> origin/ba
@test "pushes to the remote origin when there're matching non-upstream branches in multiple remotes, including origin" {
    mk_cloned_repo
    git config push.default matching
    git branch --unset-upstream
    git commit --allow-empty -m b

    git push

    assert_equal_refs origin/ba  ba
    assert_equal_refs origin2/ba ba~
    assert_equal_refs origin3/ba ba~
}

# -> it prefers the remote origin

# a (origin2/bb, origin3/bb) --- b (bb) => -
@test "does nothing when there're matching non-upstream branches in multiple remotes, but not in the remote origin" {
    mk_cloned_repo
    git config push.default matching
    git checkout -b bb
    git push origin2 bb
    git push origin3 bb
    git commit --allow-empty -m b

    git push

    assert_equal_refs origin2/bb bb~
    assert_equal_refs origin3/bb bb~
}

# -> it chooses the remote origin if it exists, and since there're no matching branches there, it does nothing

# a (origin2/ba, origin3/ba) --- b (ba) => error
@test "fails when there're matching non-upstream branches in multiple remotes and the remote origin doesn't exist" {
    mk_cloned_repo
    git config push.default matching
    git branch --unset-upstream
    git commit --allow-empty -m b
    git remote rm origin

    run git push

    assert_equal "$status" 128
    assert_output -p 'fatal: No configured push destination.'
}

# -> if the remote origin doesn't exist, there're multiple remotes to choose from
# with no reason to choose one over another, as such it fails

# other branches:

# if there's an upstream:

# a (origin/ba, origin2/ba, origin3/ba) --- b (ba), bb -> origin2/bb => ba -> origin2/ba
@test "pushes matching branches if there's an upstream" {
    mk_cloned_repo
    git config push.default matching
    git commit --allow-empty -m b
    git checkout -b bb
    git push origin2 bb
    git branch -u origin2/bb

    git push

    assert_equal_refs origin/ba  ba~
    assert_equal_refs origin2/ba ba
    assert_equal_refs origin3/ba ba~
}

# -> it uses the upstream to choose the remote

# a (origin/ba, origin2/ba, origin3/ba) --- b (ba), bb -> bc => -
@test "doesn't push branches if the upstream points to the local repo" {
    mk_cloned_repo
    git config push.default matching
    git commit --allow-empty -m b
    git checkout -b bb
    git branch bc
    git branch -u bc

    git push

    assert_equal_refs origin/ba  ba~
    assert_equal_refs origin2/ba ba~
    assert_equal_refs origin3/ba ba~
}

# if there's no upstream:

# a (origin2/ba) --- b (ba) => ba -> origin2/ba
@test "pushes matching branches when there's no upstream, but there's only 1 remote" {
    mk_cloned_repo
    git config push.default matching
    git commit --allow-empty -m b
    git checkout -b bb
    git remote rm origin
    git remote rm origin3

    git push

    assert_equal_refs origin2/ba ba
}

# a (origin/ba, origin2/ba, origin3/ba) --- b (ba) => ba -> origin/ba
@test "pushes matching branches when there's no upstream, but the remote origin exists" {
    mk_cloned_repo
    git config push.default matching
    git commit --allow-empty -m b
    git checkout -b bb

    git push

    assert_equal_refs origin/ba  ba
    assert_equal_refs origin2/ba ba~
    assert_equal_refs origin3/ba ba~
}

# a (origin2/ba, origin3/ba) --- b (ba) => error
@test "fails when there's no upstream, and the remote origin doesn't exist" {
    mk_cloned_repo
    git config push.default matching
    git commit --allow-empty -m b
    git checkout -b bb
    git remote rm origin

    run git push

    assert_equal "$status" 128
    assert_output -p 'fatal: No configured push destination.'
}

assert_equal_refs() {
    assert_equal "`git rev-parse "$1"`" "`git rev-parse "$2"`"
}

mk_cloned_repo() {
    (mk_bare_repos)
    cd "$BATS_TEST_TMPDIR"
    git clone a.git b
    cd b
    git config user.email [email protected]
    git config user.name "Your Name"
    git remote add origin2 ../a2.git
    git fetch origin2
    git remote add origin3 ../a3.git
    git fetch origin3
}

mk_bare_repos() {
    cd "$BATS_TEST_TMPDIR"
    mkdir a
    (cd a
    git init
    git branch -m ba
    git config user.email [email protected]
    git config user.name "Your Name"
    git commit --allow-empty -m a)

    git clone --bare a a.git
    cp -r a.git a2.git
    cp -r a.git a3.git
}
$ docker run --rm -itv "$PWD":/app -w /app alpine:3.21
/ # apk add git bash ncurses
/ # git clone https://github.com/bats-core/bats-core ~/.bats
/ # git clone https://github.com/bats-core/bats-support ~/.bats/lib/bats-support
/ # git clone https://github.com/bats-core/bats-assert ~/.bats/lib/bats-assert
/ # ~/.bats/bin/bats a.bats
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment