Switch branches with unfinished work

How-to Last tested 26 October 2026

You can switch branches with uncommitted changes when the switch can preserve them. If changing branches would overwrite those edits, Git normally refuses; creating a new branch at the current commit is often the simpler way to give unfinished work its own destination. A branch switch does not commit or back up the edits it carries.

Ask which destination you actually need

There are two common tasks behind “move my work to another branch.” One is to create a new branch for work started on the wrong branch. The other is to continue on a branch that already has different committed content. They can look similar in an editor, but the second task may require Git to replace files that your unfinished edits have changed.

The switch reference allows a dirty working tree when the change can preserve local work. Its default behavior refuses a switch that would lose changes1. Treat that refusal as information about the repository state. Do not immediately add an override merely to reach the branch name you intended.

First identify your current branch, the desired destination and the files you have changed. Save any relevant editor buffers so the files Git sees match the work you intend to preserve. Then decide whether a new branch is sufficient. If the destination already exists, inspect why its committed files differ before choosing how to carry your work there.

Run the complete example in an empty directory

This example starts with two branches at the same commit, carries an edit between them, and creates another branch for that edit. It then commits the edit, returns to the original branch and creates an incompatible local change. The final attempted switch is expected to fail, and the script checks that the branch and file stayed unchanged.

Download the complete example. Use Git 2.56.0 in an empty directory. The script creates its own switch-example directory and never accesses a remote repository.

#!/bin/sh
# SPDX-License-Identifier: MIT
set -eu
export TZ=UTC LC_ALL=C
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null
export GIT_AUTHOR_NAME='Example Author'
export GIT_AUTHOR_EMAIL='author@example.invalid'
export GIT_COMMITTER_NAME='Example Author'
export GIT_COMMITTER_EMAIL='author@example.invalid'
export GIT_AUTHOR_DATE='2026-10-25T12:00:00+00:00'
export GIT_COMMITTER_DATE='2026-10-25T12:00:00+00:00'
test "$(git version)" = 'git version 2.56.0'

mkdir switch-example
cd switch-example
git init -q --template= --initial-branch=main --object-format=sha1
git config commit.gpgsign false
git config core.autocrlf false
printf 'base\n' > note.txt
git add note.txt
git commit -qm 'Base note'
git branch same-point

printf 'unfinished work\n' > note.txt
before=$(git hash-object note.txt)
git switch -q same-point
test "$(git branch --show-current)" = same-point
test "$(git hash-object note.txt)" = "$before"
printf 'Existing branch at the same commit: edits carried across\n'
git status --short

git switch -qc new-work
test "$(git branch --show-current)" = new-work
test "$(git hash-object note.txt)" = "$before"
printf 'New branch: edits remain available for its next commit\n'
git add note.txt
git commit -qm 'Keep unfinished work'
git switch -q main
printf 'different local edit\n' > note.txt
before=$(git hash-object note.txt)
head_before=$(git rev-parse HEAD)
if git switch -q new-work 2>switch-refusal.txt; then
  printf 'Unexpected switch success\n' >&2
  exit 1
fi
test "$(git branch --show-current)" = main
test "$(git rev-parse HEAD)" = "$head_before"
test "$(git hash-object note.txt)" = "$before"
printf 'Conflicting target: switch refused; branch and edits retained\n'
cat switch-refusal.txt
rm switch-refusal.txt
git status --short

Case one: switch to an existing compatible branch

The first branch, same-point, begins at the same committed snapshot as main. The example replaces the contents of note.txt without staging or committing that edit. Switching to same-point succeeds, and the file’s content hash stays the same. Status still shows an unstaged modification.

That result is deliberately easy to inspect because the destination requires no different committed version of the edited file. It establishes one compatible case, not a rule that branch switching always accepts unfinished work. Other paths, staged changes and different target snapshots can make the decision more complicated.

The useful observation is that the edit remains an edit. It has not been copied into a new commit, sent anywhere or made durable by the branch name alone. Before leaving the work for later, decide how it should be recorded. Do not confuse a successful navigation step with a completed save or a reviewed commit.

Case two: create a branch for the unfinished work

Next, the example creates new-work at the current commit and switches to it. The same file hash remains present. A branch created this way starts from the current commit unless a different starting point is specified2. The new name provides a destination for subsequent commits; it does not invent a new committed snapshot of the unfinished file.

The example then stages and commits the file on new-work. That separate step is what records the changed content in its history. The original branch still points to the base commit. Returning to main therefore restores its committed base version of the file, because there are no unfinished edits left to carry from new-work at that point.

For a real task, choose a meaningful unused branch name and check its starting point before recording work. If the branch is meant to start from another branch’s newer history, creating it from the current commit may not be the intended operation. Resolve that starting-point question rather than relying on the new name to imply the correct ancestry.

Case three: keep the refusal intact

Back on main, the example writes different local edit into the same file. The destination new-work has a different committed version of that path. The switch is refused, and the script checks three facts: the current branch is still main, its commit is unchanged, and the file hash is unchanged.

The error is captured and printed, so the refusal remains part of the observable result. The script does not suppress the error and pretend the switch worked. It also does not attempt a forced switch. The next action in a real repository depends on which changes should survive and where they belong.

Pause here when the same situation occurs in real work. Read the current changes, inspect the destination’s relevant content, and choose a preservation plan. A local commit on an appropriate branch or a deliberate stash workflow may fit, but neither should be applied without checking what it will include. The stash comparison covers the latter task and its conflict boundaries.

Use checks that answer separate questions

The branch name answers where the next commit would be attached. The commit identifier identifies the current committed snapshot. Status describes changed and untracked paths3. Reading only one of these can conceal a mistaken assumption: being on the intended branch does not establish that the intended edits are staged or committed.

The file-hash checks in the example answer a narrower question: whether these exact file contents changed during each switch attempt. They are useful evidence for the demonstration, but they do not inspect an entire production repository or an editor’s unsaved buffers. In daily work, review the relevant differences as well as the branch name before continuing.

If the task is simply to remove a file from the next commit, keep the current branch and use the unstaging guide. If you need separate working directories for independent tasks, the linked-worktree guide covers that arrangement. Avoid turning a branch-switch problem into a broader history change without a reason.

Git 2.56 does not remove this decision

The new resolved-path staging and merged-branch deletion options concern different operations4. Neither makes a refused branch switch safe to force. This example keeps the default preservation guard and demonstrates a new branch, an existing compatible branch and a refused incompatible switch separately.

Not covered on this page

Sources

  1. Git 2.56.0 switch reference. ↩

  2. Git 2.56.0 branch reference. ↩

  3. Git 2.56.0 status reference. ↩

  4. Git 2.56.0 release notes. ↩

How we test: methodology. Found a mistake? Corrections.