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 upstreamorigin/ba2
and there'sorigin/ba
, it will chooseorigin/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