Skip to content

Instantly share code, notes, and snippets.

@x-yuri
Last active December 22, 2024 07:12
Show Gist options
  • Save x-yuri/dd442da9f8dc242a4ac605fe59bfc7e5 to your computer and use it in GitHub Desktop.
Save x-yuri/dd442da9f8dc242a4ac605fe59bfc7e5 to your computer and use it in GitHub Desktop.
git pull
$ git pull [<repository> [<refspec>...]]

Basically does git fetch [<repository> [<refspec>...]] + git merge FETCH_HEAD (git rebase FETCH_HEAD).

If multiple <refspec>'s are passed or a <refspec> resolves to multiple references, then all the resolved references are merged into the current branch (rebase is possible only when one reference is to be rebased onto). If there's only one reference, it's merged into the current branch (the current branch is rebased onto it).

If no <refspec>'s are passed, then the upstream branch is merged into the current branch (the current branch is rebased onto the upstream branch).

If the current branch has no upstream branch, it fails.

The relevant docs:

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

<refspec>

Specifies which refs to fetch and which local refs to update. When no <refspec>s appear on the command line, the refs to fetch are read from remote.<repository>.fetch variables instead (see the section "CONFIGURED REMOTE-TRACKING BRANCHES" in git-fetch[1]).

There is a difference between listing multiple <refspec> directly on git pull command line and having multiple remote.<repository>.fetch entries in your configuration for a <repository> and running a git pull command without any explicit <refspec> parameters. <refspec>s listed explicitly on the command line are always merged into the current branch after fetching. In other words, if you list more than one remote ref, git pull will create an Octopus merge. On the other hand, if you do not list any explicit <refspec> parameter on the command line, git pull will fetch all the <refspec>s it finds in the remote.<repository>.fetch configuration and merge only the first <refspec> found into the current branch. This is because making an Octopus from remote refs is rarely done, while keeping track of multiple remote heads in one-go by fetching more than one is often useful.

https://git-scm.com/docs/git-pull#Documentation/git-pull.txt-ltrefspecgt

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.

In order to determine what remote branches to fetch (and optionally store in the remote-tracking branches) when the command is run without any refspec parameters on the command line, values of the configuration variable remote.<origin>.fetch are consulted, and if there aren’t any, $GIT_DIR/remotes/<origin> is consulted and its Pull: lines are used. In addition to the refspec formats described in the OPTIONS section, you can have a globbing refspec that looks like this:

refs/heads/*:refs/remotes/origin/*

A globbing refspec must have a non-empty RHS (i.e. must store what were fetched in remote-tracking branches), and its LHS and RHS must end with /*. The above specifies that all remote branches are tracked using remote-tracking branches in refs/remotes/origin/ hierarchy under the same name.

The rule to determine which remote branch to merge after fetching is a bit involved, in order not to break backward compatibility.

If explicit refspecs were given on the command line of git pull, they are all merged.

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.

  • Merge into the current branch the remote branch next:

    $ git pull origin next
    

    This leaves a copy of next temporarily in FETCH_HEAD, and updates the remote-tracking branch origin/next. The same can be done by invoking fetch and merge:

    $ git fetch origin
    $ git merge origin/next
    

https://git-scm.com/docs/git-pull#_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 (1 commit, 1 local branch ba)
# A (HEAD -> ba, origin/ba)

@test "does an octopus merge when more than 1 reference" {
    start_cloned_repo
    (cd ../a && git commit --allow-empty -m B \
        && git checkout -b bb HEAD~ && git commit --allow-empty -m C)

    # git pull --no-ff origin 'refs/heads/*:refs/remotes/origin/*'
    git pull --no-ff origin ba bb

    assert_equal "`
        git show --pretty=%P -s | tr ' ' '\n' | sort
    `" "`
        { git rev-parse HEAD~
        git rev-parse origin/ba
        git rev-parse origin/bb; } | sort
    `"
}

@test "pulls the specified reference" {
    start_cloned_repo
    (cd ../a && git commit --allow-empty -m B)
    git checkout -b bb

    git pull --no-ff origin ba

    assert_equal_sha HEAD^2 origin/ba
}

@test "pulls the upstream branch" {
    start_cloned_repo
    git push origin ba:bb; git branch -u origin/bb
    (cd ../a && git checkout bb && git commit --allow-empty -m B)

    git pull --no-ff origin

    assert_equal_sha HEAD^2 origin/bb
}

@test "fails when no upstream branch" {
    start_cloned_repo
    git branch --unset-upstream

    run git pull --no-ff origin

    assert_equal "$status" 1
    assert_output -p "`ml \
        "You asked to pull from the remote 'origin', but did not specify" \
        'a branch. Because this is not the default configured remote' \
        'for your current branch, you must specify a branch on the command line.'`"
}

@test "fails when the upstream branch is in the other remote" {
    start_cloned_repo
    git remote add origin2 ../a2

    run git pull --no-ff origin2

    assert_equal "$status" 1
    assert_output -p "`ml \
        "You asked to pull from the remote 'origin2', but did not specify" \
        'a branch. Because this is not the default configured remote' \
        'for your current branch, you must specify a branch on the command line.'`"
}

@test "pulls from the upstream's remote" {
    start_cloned_repo
    (cd ../a2 && git commit --allow-empty -m B)
    git remote add origin2 ../a2
    git fetch origin2; git branch -u origin2/ba

    git pull --no-ff

    assert_equal_sha HEAD^2 origin2/ba
}

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

assert_not_equal_sha() {
    ! assert_equal_sha "$1" "$2"
}

assert_branch() {
    git show-ref --verify --quiet "$1"
}

refute_branch() {
    run git show-ref --verify --quiet "$1"
    assert_equal "$status" 1
}

start_cloned_repo() {
    (mkrepos)
    cd "$BATS_TEST_TMPDIR"
    git clone a b
    cd b
    git config user.email [email protected]
    git config user.name "Your Name"
}

mkrepos() {
    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)

    cp -r a a2
}

ml() { for l; do printf '%s\n' "$l"; done; }
$ 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