Skip to content

Instantly share code, notes, and snippets.

@x-yuri
Last active December 22, 2024 07:11
Show Gist options
  • Save x-yuri/cf2b8ece688206e73904286c3da23b82 to your computer and use it in GitHub Desktop.
Save x-yuri/cf2b8ece688206e73904286c3da23b82 to your computer and use it in GitHub Desktop.
git: upstream branches

git: upstream branches

branch.<name>.remote and branch.<name>.merge together define the upstream branch for the given branch (if they're set, the given branch has an upstream branch). . as a remote denotes the local repository.

E.g. if branch.dev.remote = origin and branch.dev.merge = refs/heads/dev, then origin/dev (the remote-tracking branch of the remote branch dev) is an upstream of dev.

Or if branch.a.remote = . and branch.a.merge = refs/heads/b, then b is an upstream of a.

The upstream branch tells (under certain circumstances):

  • git fetch which branch to fetch and/or from which remote
  • git pull which branch to merge (which branch to rebase onto)
  • git push which remote to push to and/or to which branch

The relevant docs:

branch.<name>.remote

When on branch <name>, it tells git fetch and git push which remote to fetch from or push to. The remote to push to may be overridden with remote.pushDefault (for all branches). The remote to push to, for the current branch, may be further overridden by branch.<name>.pushRemote. If no remote is configured, or if you are not on any branch and there is more than one remote defined in the repository, it defaults to origin for fetching and remote.pushDefault for pushing. Additionally, . (a period) is the current local repository (a dot-repository), see branch.<name>.merge's final note below.

https://git-scm.com/docs/git-config#Documentation/git-config.txt-branchltnamegtremote

branch.<name>.merge

Defines, together with branch.<name>.remote, the upstream branch for the given branch. It tells git fetch/git pull/git rebase which branch to merge and can also affect git push (see push.default). When in branch <name>, it tells git fetch the default refspec to be marked for merging in FETCH_HEAD. The value is handled like the remote part of a refspec, and must match a ref which is fetched from the remote given by "branch.<name>.remote". The merge information is used by git pull (which first calls git fetch) to lookup the default branch for merging. Without this option, git pull defaults to merge the first refspec fetched. Specify multiple values to get an octopus merge. If you wish to setup git pull so that it merges into <name> from another branch in the local repository, you can point branch.<name>.merge to the desired branch, and use the relative path setting . (a period) for branch.<name>.remote.

https://git-scm.com/docs/git-config#Documentation/git-config.txt-branchltnamegtmerge

When no remote is specified, by default the origin remote will be used, unless there’s an upstream branch configured for the current branch.

https://git-scm.com/docs/git-fetch#_description

Default values for <repository> and <branch> are read from the "remote" and "merge" configuration for the current branch as set by git-branch[1] --track.

https://git-scm.com/docs/git-pull#_description

Often people use git pull without giving any parameter. Traditionally, this has been equivalent to saying git pull origin. However, when configuration branch.<name>.remote is present while on branch <name>, that value is used instead of origin.

When no refspec was given on the command line, then git pull uses the refspec from the configuration or $GIT_DIR/remotes/<origin>. In such cases, the following rules apply:

  1. If branch.<name>.merge configuration for the current branch <name> exists, that is the name of the branch at the remote site that is merged.

  2. If the refspec is a globbing one, nothing is merged.

  3. Otherwise the remote branch of the first refspec is merged.

https://git-scm.com/docs/git-pull#_default_behaviour

  • Update the remote-tracking branches for the repository you cloned from, then merge one of them into your current branch:

    $ git pull
    $ git pull origin
    

    Normally the branch merged in is the HEAD of the remote repository, but the choice is determined by the branch.<name>.remote and branch.<name>.merge options; see git-config[1] for details.

https://git-scm.com/docs/git-pull#_examples

When the command line does not specify where to push with the <repository> argument, branch.*.remote configuration for the current branch is consulted to determine where to push. If the configuration is missing, it defaults to origin.

https://git-scm.com/docs/git-push#_description

git push

Works like git push <remote>, where <remote> is the current branch’s remote (or origin, if no remote is configured for the current branch).

git push origin

Without additional configuration, pushes the current branch to the configured upstream (branch.<name>.merge configuration variable) if it has the same name as the current branch, and errors out without pushing otherwise.

The default behavior of this command when no <refspec> is given can be configured by setting the push option of the remote, or the push.default configuration variable.

For example, to default to pushing only the current branch to origin use git config remote.origin.push HEAD. Any valid <refspec> (like the ones in the examples below) can be configured as the default for git push origin.

https://git-scm.com/docs/git-push#_examples

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 (2 commits, 1 local branch dev):
# a (origin/master) --- b (HEAD -> dev, origin/dev)

@test "branch with an upstream has branch.<name>.remote and branch.<name>.merge set" {
    start_cloned_repo

    assert_equal "`git config branch.dev.remote`" origin
    assert_equal "`git config branch.dev.merge`" refs/heads/dev
    assert_upstream dev origin/dev
}

@test "branch without an upstream doesn't have branch.<name>.remote and branch.<name>.merge set" {
    start_cloned_repo
    git checkout -b feature

    assert_equal "`git config branch.feature.remote`" ''
    assert_equal "`git config branch.feature.merge`" ''
    refute_upstream feature
}

@test "can't set an upstream branch when no remote.<name>.fetch" {
    start_cloned_repo
    git config unset remote.origin.fetch

    run git branch -u origin/dev

    assert_equal "$status" 128
    assert_output -p "fatal: cannot set up tracking information; starting point 'origin/dev' is not a branch"
}

assert_upstream() {
    run git rev-parse --abbrev-ref "$1@{upstream}"
    assert_output "$2"
}

refute_upstream() {
    run git rev-parse --abbrev-ref "$1@{upstream}"
    if [ "${2-}" ]; then
        refute_output "$2"
    else
        assert_output "fatal: no upstream configured for branch '$1'"
    fi
}

start_cloned_repo() {
    (mkrepo)
    cd "$BATS_TEST_TMPDIR"
    git clone a b
    cd b
}

mkrepo() {
    cd "$BATS_TEST_TMPDIR"
    mkdir a
    cd a
    git init
    git config user.email [email protected]
    git config user.name "Your Name"
    git commit --allow-empty -m a
    git checkout -b dev
    git commit --allow-empty -m b
}
$ 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