As a developer, you often find yourself working on a new feature or fix in the main codebase branch, usually master
. After making some changes, you realize you should have created a separate branch for that work. However, since the changes are uncommitted, you don‘t want to lose them. Luckily, Git provides a simple way to move all uncommitted work to a new branch.
Why Create Feature Branches?
There are a few key reasons for moving existing uncommitted work to a new branch:
-
Keep mainline branches clean – The
master
ormain
branches should contain shippable code, ready for release at any time. Unfinished, experimental work should happen in feature branches. -
Separate concerns – Branches let you context switch between different tasks or ideas. Compartmentalization reduces complexity.
-
Preserve work in progress – By diverting changes to a branch, you don‘t lose anything already done.
-
Facilitate pull requests – Branches cleanly capture a related set of commits for code review and integration.
Branching is a pivotal Git workflow tool for any non-trivial project. In Stack Overflow‘s 2021 survey, over 90% of developers reported using Git, underscoring its widespread adoption. Branching and merging enables developers to collaborate efficiently on the same codebase.
According to the authoritative Git Book published by Git itself:
Branching means you diverge from the main line of development and continue to do work without messing with that main line.
So why isn‘t all development done directly on master
? Long-running experiments may destabilize the code or feature toggles complicate testing. Branch-based development compartmentalizes changes related to a task, bug fix, or concept.
Proper use of branchingstrategies also leads to higher quality software.
Viewing File Changes
Before moving anything, let‘s inspect existing uncommitted changes. Navigate to the local Git repository in the terminal then run:
git status
This displays working tree changes divided into files that are:
-
Untracked – Unknown to Git and not staged for commit yet. Usually newly created files.
-
Modified – Known to Git but contain changes that are unstaged. Existing tracked files with edits.
-
Staged – Changes explicitly flagged and ready to commit.
For example:
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/App.js
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
new-feature.js
Here src/App.js
contains staged changes ready to commit. README.md
shows modified but unstaged changes. And new-feature.js
is an untracked file. Next, we‘ll move all changes to a new branch.
Creating a Branch
Creating a branch is fast and easy in Git. Use git checkout
with the -b
flag:
git checkout -b new-branch
This simultaneously creates and switches to the branch called new-branch
.
An alternative is using git switch
introduced in Git 2.23:
git switch -c new-branch
The key things to note are:
- checkout/switch: Move the HEAD reference pointer to the branch and update working directory files.
- -b/ -c: Pass this option to create the branch if it doesn‘t already exist.
These commands provide a shortcut to link working directory changes to a new branch instantly.
Changing branches with Git checkout or switch (Source)
Conceptually branches represent diverging timelines of commits across parallel universes. By switching position with HEAD
, you expose files tracked in that particular line of commits.
How Checkout vs Switch Compares
git checkout
and git switch
offer similar functionality for changing branches, with some key differences:
Operation | Checkout | Switch |
---|---|---|
Change branches | Yes | Yes |
Create new branches | Yes | Yes |
Restore files from HEAD | Yes | No |
git restore
replaces git checkout -- <file>
for discarding changes in the Git 2.23+ release. Using switch
signals the intent to only change branches, while checkout
does double duty.
Overall switch
offers a more focused, dedicated command improving clarity. Checkout has been around longer so still useful for restoring files when needed.
Confirm Branch Creation
After creating a branch, verify it exists by running git branch
. This prints all branch references:
main
* new-branch
The preceding *
indicates the current HEAD position – in this case, pointing to new-branch
.
Rename branches with:
git branch -m new-name
Descriptive names categorize work such as feature/new-modal
or bug/login-error
. Well organized branches simplify management of a repository with many divergent efforts.
According to hosting service GitHub‘s recommendations in their blog:
Branch names should be all lowercase, with words separated by hyphens (-).
Adhering to conventions like these makes collaboration easier.
Preserving Uncommitted Changes
The major outcome is uncommitted work transfers over to the new Git branch. Check files match the earlier git status
output.
If previously untracked, those same files now show under changes not staged for commit:
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
new-feature.js
Modifications also persist intact after the branch changeover. However, changes still need to be added and committed as usual.
Existing edits seamlessly divert to the new branch without losing work in progress. This enables context switching between multiple efforts by compartmentalizing changes.
Integrating Changes Via Pull Requests
After completing a feature or fix, integrate changes back into main
via a pull request (PR). This issues a merge commit applied to the base branch, fast-forwarding its commit history.
Merging a topic branch into main via pull request
The PR consolidates all commits associated with the branch into mainline. After approval and merging, delete the branch reference to keep the environment tidy.
Weighing Git Merge Strategies
When integrating branches, there are a few merge techniques to consider:
Method | Description | Pros | Cons |
---|---|---|---|
Fast forward | Move pointer forward | Simple, linear history | Doesn‘t preserve branch point |
No fast forward | Always generate merge commit | Capture branch history | More merge commits |
Rebase | Re-apply commits | Clean commit tree | Rewrites history |
Fast forward directly plays forward main if possible, while no fast forward forces a merge commit. Rebasing replays branch changes on top of main updates.
There are valid use cases for each depending on the development flow style preferred.
Summary
Moving uncommitted work into a new Git branch avoids losing changes and separates ongoing efforts.
- Use
git checkout -b
orgit switch -c
to swiftly create a branch and switch active files - All uncommitted edits carry over to the new branch worktree
- Name branches clearly for organizational purposes
- Integrate via pull request once work completes
- Delete branch after merging to main
Branch-based development takes time to get used to but soon feels second nature. This unlocks decentralized workflows enabling developers to collaborate efficiently on the same codebase.