- Git Branches allow us to develop features independently of "production" code to avoid affecting what our teammates and users see before we are ready.
- How to create a new branch
- How to move between branches
- How to merge 1 branch to another and resolve merge conflicts
A Git Branch is an independent series of commits. Every Git repo starts with a single branch, typically main
, which we can imagine to be a linear series of commits.
Using multiple branches allows software engineers to develop new features based on production code in main
without affecting main
, even after pushing to GitHub. We typically refer to non-main
branches as "feature branches". Feature branches can be for changes as small as a typo and as large as new products.
Feature branches are independent series of commits that typically "branch" from main
, and merge back into main
after we have completed and tested the new feature.
We can delete feature branches after merging them to main
.
At large tech companies, 1000s of engineers can be working on independent feature branches that branch from main
and merge back to main
at different points in time.
Create new branches with git checkout -b
. git checkout
is the command to switch, or "checkout" branches, and the -b
flag creates a new branch and checks it out. Branch names use kebab-case (lowercase with hyphens between words) by default, and we will use my-feature
as our example branch name.
git checkout -b my-feature
Verify we are on the new my-feature
branch with git branch
.
react % git checkout -b my-feature
Switched to a new branch 'my-feature'
react % git branch
main
* my-feature
react %
Now we can make commits on my-feature
that build on the state of main
when we created my-feature
. Changes on my-feature
will not affect any other branch in our repo.
While working on feature branches we may wish to periodically checkout other branches such as main
to verify our changes are still compatible with theirs. To change back to the main
branch, run git checkout main
. We may also wish to git pull
when on main
to pull any new changes from GitHub to main
. Run git checkout my-feature
to go back to the my-feature
branch.
Once done with our feature on our feature branch, we can merge our changes to main
for teammates and users to use. When working independently we can perform the merge locally, but when working in a team we typically perform the merge via GitHub to give teammates a chance to review our changes through a "pull request".
- Checkout the branch we want to merge into. For example, if we want to merge changes on our feature branch to
main
, checkoutmain
. - Run
git merge
followed by the source branch name, e.g.git merge my-feature
. - Git will combine commits from both branches and create a "merge commit" to resolve any differences. Run
git log
to view the merge commit and verify merge success. - Delete the feature branch locally with
git branch -d
followed by the feature branch name, e.g.git branch -d my-feature
. - Once ready, push latest changes in
main
to GitHub.
- Checkout and verify we are on our feature branch with
git branch
- Run
git push
to push latest commits from our feature branch to GitHub. If this is our first time pushing this feature branch to GitHub, we may have to rungit push --set-upstream origin
followed by our feature branch name, e.g.git push --set-upstream origin my-feature
. - See instructions in 0.3.1: Pull Requests for how to create and merge a pull request in GitHub.
We may see the following output when pushing a feature branch to GitHub for the first time with git push
. To resolve, enter the command Git suggests: git push --set upstream origin my-feature
, where my-feature
is the name of our feature branch.
{% code title="Git Push failure first time pushing feature branch to GitHub" %}
react % git push
fatal: The current branch my-feature has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin my-feature
react % git push --set-upstream origin my-feature
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'my-feature' on GitHub by visiting:
remote: https://github.com/kai-rocket/react/pull/new/my-feature
remote:
To https://github.com/kai-rocket/react.git
* [new branch] my-feature -> my-feature
Branch 'my-feature' set up to track remote branch 'my-feature' from 'origin'.
react %
{% endcode %}
upstream
refers to where our code should be hosted. origin
refers to our GitHub repo or where we cloned our repo from. my-feature
tells Git to create a new branch called my-feature
in GitHub and by default push changes from the local my-feature
branch to the GitHub my-feature
branch.
After setting upstream once for a branch, we can run git push
without arguments for subsequent pushes from this branch.
In addition to merging feature branches to main
, another common workflow is to merge latest changes from main
into our feature branch. This minimises chances of merge conflicts when we merge our feature branch to main
, especially if there have been big changes to main
since we started our feature.
- Checkout
main
withgit checkout main
and pull latest changes from GitHub withgit pull
. - Checkout our feature branch, e.g.
git checkout my-feature
. - Run
git merge main
to mergemain
into our feature branch. If we're lucky Git will merge the changes automatically. If not we will need to resolve merge conflicts manually.
{% embed url="https://youtu.be/56B7MOgm_CE" %} Demo of how merge conflicts happen and how to resolve them {% endembed %}
Merge conflicts are situations when Git cannot automatically merge changes from 2 branches, for example if 2 branches change the same line of code differently. We can minimise merge conflicts by actively communicating with teammates to work on different files or functions, but generally merge conflicts are a standard feature of software engineering.
VS Code highlights differences in files with conflicts. The lines surrounded by <<<<<<< HEAD
and =======
are changes from the branch we are on, and the lines surrounded by =======
and >>>>>>> main
are changes from the incoming branch.
Once we have a merge conflict we must resolve it before writing new code, such that each commit in our commit history continues to describe a specific change. If we are unable to resolve conflicts now or merged by accident, we can abort merge with git merge --abort
, which will revert our repo state to just before we ran git merge
.
After Git tells us we have merge conflicts, use git status
to confirm which files have conflicts.
Open each file with conflicts and resolve conflicts in each file by removing lines starting with <<<<<<<
, =======
and >>>>>>>
and updating the code to what it should be. We can use VS Code's Accept Current/Incoming/Both Changes buttons and/or manually edit the files.
Once we have resolved all conflicts, verify our app still works as expected. Once satisfied with our changes, git add
the resolved files to add them to staging area for commit.
Commit changes to finalise Git's merge commit and complete merge.
After committing, git status
should no longer mention conflicts.
We can verify merge success by checking commit history in Git Logs with git log
.
-
Create a new repo.
mkdir poems cd poems git init
-
Create a poem about water in
water-poem.txt
. Commit this file to the repo. -
Create and checkout a new branch to edit the water poem.
git checkout -b water-poem-edits
-
Edit the water poem and commit it to the new branch you just created.
-
List all branches.
git branch
-
Checkout
main
.git checkout main
-
Verify
water-poem.txt
has reverted to the version onmain
. -
Create a new poem about sandwiches in a new file and commit it.
-
Checkout the water poem branch.
git checkout water-poem-edits
-
Verify the sandwich poem does not exist in the water poem branch.
-
Checkout
main
and merge the water poem edits from the water poem branch. -
Verify
water-poem.txt
contains changes from the water poem branch. -
Delete the water poem branch with
git branch -d water-poem-edits
.
-
Start from the same repo as the previous exercise.
-
Make a new branch for edits to the sandwich poem.
git checkout -b sandwich-poem-edits
-
While on the sandwich branch, add a line to the poem and change the line that's currently there.
-
Commit the changes on the sandwich branch.
-
Checkout
main
. To create a merge conflict we will commit new changes to the same lines on themain
branch. -
Make a change to the sandwich poem and commit it.
-
Merge the sandwich branch into
main
. -
We should observe a merge conflict.
-
Open the sandwich poem file to see merge conflict symbols from Git.
-
Resolve the merge conflict as per instructions above.