Skip to content

Instantly share code, notes, and snippets.

@x-yuri
Last active December 27, 2024 00:09
Show Gist options
  • Save x-yuri/32d85163dc5b97ef4b49b5489686306c to your computer and use it in GitHub Desktop.
Save x-yuri/32d85163dc5b97ef4b49b5489686306c to your computer and use it in GitHub Desktop.
git checkout -b

git checkout -b

Generally, to create a branch and switch to it, here's the command:

$ git checkout -b <branch> [<start-point>]

If you want to create <branch> and made <remote>/<branch> its starting point and upstream though, you can omit the -b <branch> part:

$ git checkout -t <remote>/<branch>

This also works with branches that has / in their names (e.g. git checkout -t origin/feature/a).

This can be further reduced to:

$ git checkout <branch>

if the branch doesn't exist locally and there's exactly one remote where a branch with this name exists.

Under these last conditions the following commands are equivalent:

$ git checkout <branch>
$ git checkout -t <remote>/<branch>
$ git checkout -b <branch> -t <remote>/<branch>

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 repo (3 commits, 3 local branches ba/b, bc, bd):
# ca/b (ba/b) --- cc (bc) --- cd (HEAD -> bd)
#
# the state of the cloned repo (3 commits, 1 local branch bd):
# ca/b (origin/ba/b) --- cc (origin/bc) --- cd (HEAD -> bd, origin/bd)

@test "\`git checkout -b <branch>\` creates a branch pointing to HEAD with no upstream" {
    mkrepo

    git checkout -b be

    assert_branch be
    assert_equal_refs be bd
    refute_upstream be
}

@test "\`git checkout -b <branch> <start-point>\` creates a branch pointing to <start-point>" {
    mkrepo

    git checkout -b be bc

    assert_branch be
    assert_equal_refs be bc
    refute_upstream be
}

@test "\`git checkout -t <remote>/<branch>\` creates a branch <branch> pointing to <remote>/<branch> with the corresponding upstream" {
    mk_cloned_repo

    git checkout -t origin/bc

    assert_branch bc
    assert_equal_refs bc origin/bc
    assert_upstream bc origin/bc
}

@test "\`git checkout -t <remote>/<branch>\` handles branch names with /" {
    mk_cloned_repo

    git checkout -t origin/ba/b

    assert_branch ba/b
    assert_equal_refs ba/b origin/ba/b
    assert_upstream ba/b origin/ba/b
}

@test "\`git checkout <branch>\` fails if <branch> doesn't exist and there're no branches with the matching name in the remotes" {
    mk_cloned_repo

    run git checkout be

    assert_equal "$status" 1
    assert_output "error: pathspec 'be' did not match any file(s) known to git"
}

@test "\`git checkout <branch>\` fails if <branch> doesn't exist and there're more than one branch with the matching name in the remotes" {
    mk_cloned_repo
    git remote add origin2 ../a
    git fetch origin2

    run git checkout bc

    assert_equal "$status" 128
    assert_output -p "fatal: 'bc' matched multiple (2) remote tracking branches"
}

@test "\`git checkout <branch>\` creates a branch, pointing to <remote>/<branch> with the corresponding upstream" {
    mk_cloned_repo

    git checkout bc

    assert_branch bc
    assert_equal_refs bc origin/bc
    assert_upstream bc origin/bc
}

assert_branch() {
    run git branch --list "$1"
    assert_output -p "$1"
}

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

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
}

mk_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 branch -m ba/b
    git config user.email [email protected]
    git config user.name "Your Name"
    git commit --allow-empty -m ca/b
    git checkout -b bc
    git commit --allow-empty -m cc
    git checkout -b bd
    git commit --allow-empty -m cd
}
$ 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