Target Usage: To make use of multiple timelines of work in a local repository.
Motivation: At times, you need to do multiple parallel changes to files (e.g., to try two alternative implementations of the same feature).
Lesson plan:
T6L1. Creating Branches covers that part.
T6L2. Merging Branches covers that part.
T6L3. Resolving Merge Conflicts covers that part.
T6L4. Renaming Branches covers that part.
T6L5. Deleting Branches covers that part.
T6L6. Working on Multiple Branches with Worktrees covers that part.
To work in parallel timelines, you can use Git branches.
Often, we need to make multiple parallel changes to files in a repository without one change affecting the others.

One such situation is when you want to experiment with multiple alternative fixes to a bug in parallel.
For example, suppose you notice a bug after a few commits, as shown on the left, and want to try alternative fixes.

If you simply create more commits, the two fixes can mix together, interfere with each other, and get tangled with your main code.
You could copy the repository into two folders and try one fix in each. But then you would have three repositories to manage and would need to copy changes manually when choosing a fix.
Instead, we need a way to maintain multiple parallel timelines in the same repository.

Because Git revision graphs are implemented as , they can already maintain multiple parallel timelines. For example, Git can maintain two timelines that diverge from the main timeline at commit c, one for each bug fix. You can then switch between them, compare the fixes, and choose which one to keep.

Branches let us manage diverged timelines in a practical way. A branch name points to the latest commit in a timeline, making that timeline easy to refer to.
Therefore, a branch is conceptually a named timeline of commits, implemented as a label/reference (ref for short) that points to the latest commit in that timeline. In the example on the left, there are three branches: main, fix1, and fix2.
The latest commit that a points to is called the tip of the branch. For example, c is the tip of the main branch while f1 is the tip of the fix1 branch.
All commits reachable from the branch ref are considered part of the branch. Reachability follows each commit's 'parent' link. In the example below, commits c, b, and a are on main because Git can start from the ref main and traverse to those commits through parent links. Similarly, commits f1, e1, d1, c, b, and a are on fix1.
Clarification on the 'start' of a branch
We often call the point where a branch diverges from another branch the 'start' of the branch. However, technically, a branch doesn't have a start point. Since all commits reachable from a branch ref are part of the branch, the branch could be said to start at any of those commits.
In the examples above, commit c could be called the start of branches fix1 and fix2, although commits a and b are also in those branches.

The HEAD is a special ref that points to the branch ref of the branch you are currently on, also called the current branch or the active branch. In the example on the left, fix1 is the active branch.
Git automatically updates the working directory to match the branch tip. As a result, changes made only in other diverged branches do not pollute it. This lets you work on one timeline in isolation.
Caveat: When switching branches, uncommitted changes may be carried across, conflict, or block the switch. More on this later.
In the example below, observe how the file in the working directory changes as we change the active branch.
Next, let us look at how branches behave as you add commits.

After you initialize a repo, Git already has a HEAD ref pointing to a branch ref. master is Git's default name for that initial branch, although you can configure another default. main is more common these days (and is the default used by Git-Mastery), so we will use it here.
At the start, you already have a branch but without any commits on it.

When you create the first commit, the main branch ref points to it, making that commit the tip of main.
The first commit of a repo doesn't have a parent commit.

When you add a new commit, two things happen:
- First, the new commit uses the commit
HEADpoints to as its parent. Here, the new commit uses commitaas its parent.

- Second, the branch ref that
HEADpoints to moves to the new commit. In this example, themainbranch ref will move to the new commitb.
TheHEADcontinues to point to the same branch ref, which means themainbranch is still the active branch.
New commits go into the branch you are currently on, and the branch ref moves to the new commit, so HEAD too points to the new commit through that branch ref.
Next, let's add branches beyond Git's initial branch.

When you add a new branch, Git adds a branch ref pointing to a commit. Unless you specify another commit, it points to the tip of the current branch.
In the example on the left, the new fix1 branch ref points to the same commit as main.

If you want subsequent commits to go into the new branch, make it active by switching to it. Then, HEAD points to the new branch ref.
In the example on the left, HEAD now points to fix1, making fix1 active.

Now, a new commit d1 has been added to fix1. The fix1 ref has moved to the new commit, and HEAD points to it via the fix1 branch ref. The main branch ref remains where it is.
Revision graphs vary by Git client, so your graph's colors, positions, and orientation might not match these diagrams exactly.
Preparation
Option 1: Create a fresh sandbox using the Git-Mastery app
- i.Navigate inside the
gitmastery-exercisesfolder. - ii.Run the
gitmastery download hp-create-branchcommand.
The sandbox will be set up inside the gitmastery-exercises/hp-create-branch folder.
Option 2: Manually set up a sandbox
Create a repo named sports:
mkdir sports
cd sports
git init -b main
echo -e "Arnold Palmer\nTiger Woods" > golf.txt
git stage golf.txt
git commit -m "Add golf.txt"
echo -e "Pete Sampras\nRoger Federer\nSerena Williams" > tennis.txt
git stage tennis.txt
git commit -m "Add tennis.txt"
echo -e "Pele\nMaradona" > football.txt
git stage football.txt
git commit -m "Add football.txt"
1 Observe that you are on the branch called main.
git status
On branch main
2 Start a branch named feature1 and switch to the new branch.
Use git branch to create a branch and git checkout to switch to it.
git branch feature1
git checkout feature1
Shortcut to create and switch in one step:
git checkout -b feature1
Switched to a new branch 'feature1'
The new switch command
You can use the more modern alternative git switch instead of git checkout.
To create a new branch and switch to it:
git branch feature1
git switch feature1
One-step shortcut (by using -c or --create flag):
git switch -c feature1
To list the branches in the repo, use the git branch (or the more specific git branch --list) command:
git branch
* feature1
main
The * indicates the current branch. The main branch is still there, but you are now on the feature1 branch.
Click on the Branch button on the main menu. In the next dialog, enter the branch name and click Create Branch.
Note that feature1 is now the current branch. Sourcetree switches automatically when Checkout New Branch was selected in the dialog.
3 Create some commits in the new branch, as follows.
- 3.1Add a file named
boxing.txt, stage it, and commit it.echo "Muhammad Ali" > boxing.txt git stage boxing.txt git commit -m "Add boxing.txt" - 3.2Observe how commits you add while on
feature1become part of that branch.
Observe how thefeature1ref andHEADref move to the new commit.
As before, you can use the git log --oneline --decorate command for this.
- Sourcetree sometimes represents the local repo's
HEADref as , as shown below:
- The
HEADref is not shown in the UI if it is already pointing at the active branch.
- 3.3Add more text to
boxing.txt, stage the changes, and commit it. This commit is also added tofeature1.echo "Mike Tyson" >> boxing.txt git commit -am "Add Tyson to boxing.txt"
4 Switch to the main branch. Note how the changes you made in the feature1 branch are no longer in the working directory.
git switch main
Double-click the main branch.
5 Add a commit to the main branch. Let’s imagine it’s a bug fix.
To keep things simple for the time being, this commit should not involve the boxing.txt file that you changed in the feature1 branch. Of course, this is easily done, as the boxing.txt file you added in the feature1 branch is not even visible when you are in the main branch.
echo "Martina Navratilova" >> tennis.txt
git commit -am "Add Martina to tennis.txt"
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m0"
commit id: "m1"
commit id: "m2"
branch feature1
commit id: "f1"
commit id: "[feature1] f2"
checkout main
commit id: "[HEAD → main] m3"
checkout feature1
6 Switch between the two branches and see how the working directory changes. You now have two parallel timelines that you can freely switch between.
done!
You can also start a branch from an earlier commit, not only from the latest commit in the current branch. Check out the commit you want the new branch to start from.
Preparation
Option 1: Create a fresh sandbox using the Git-Mastery app
- i.Navigate inside the
gitmastery-exercisesfolder. - ii.Run the
gitmastery download hp-early-branchcommand.
The sandbox will be set up inside the gitmastery-exercises/hp-early-branch folder.
Option 2: Continue with the sports repo from the previous hands-on practical
Scenario Suppose we want a branch with an alternative version of the feature1 content.
Target Create a new branch from the commit where feature1 started, as shown below:
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m0"
commit id: "m1"
commit id: "m2"
branch feature1
branch feature1-alt
checkout feature1
commit id: "f1"
commit id: "[feature1] f2"
checkout main
commit id: "[HEAD → main] m3"
checkout feature1-alt
commit id: "[HEAD → feature1-alt] a1"
Avoid this rookie mistake!
Before creating a branch, make sure you are at the commit where the new branch should start, as that is where Git will create the new branch by default.
1 Switch to the main branch.
2 Check out the commit where feature1 diverged from main (e.g., git checkout HEAD~1). This creates
a 'detached' HEAD. You are now at the commit where the new branch should diverge.
3 Create a new branch feature1-alt and switch to it (e.g., git switch -c feature1-alt). HEAD now points to this new branch and is no longer 'detached'.
PRO-TIP: Moving and creating a branch in one shot
Suppose you are on feature1 and want to create feature2 from main, then switch to it. Normally, that takes two steps:
git switch main # switch to the intended base branch first
git switch -c feature2 # create the new branch and switch to it
Use git switch -c <new-branch> <start-point> to do both in one step:
git switch -c feature2 main
Similarly, the following will create the new branch to start from one commit behind the tip of the main branch:
git switch -c feature2 main~1
4 Add a commit on the new branch. Example:
echo "Venus Williams" >> tennis.txt
git commit -am "Add Venus to tennis.txt"
done!
Most work done on branches eventually gets merged together.
Merging combines the changes from one branch into another, bringing their diverged timelines back together.

The branch you are merging into (the branch you are currently on) is called the destination branch (other terms: receiving branch, target branch).
The branch you are merging is the source branch (other terms: incoming branch, merge branch).
In our example, main is the destination branch and fix1 is the source branch.

When you merge, Git compares how the two branches have diverged since their merge base (the most recent common ancestor commit). In the example on the left, commit c is their merge base.
Git then applies the source branch's changes to your current branch. Normally, this creates a new commit in the destination branch . That merge commit records the combined changes.
A typical two-branch merge commit has two parent commits. In the example above, merge commit f has both d and e as parents. The parent commit on the is the first parent, and the parent commit on the is the second parent. In our example, when fix1 is merged into main, d is the first parent and e1 is the second parent.
Merging is directional. Merging fix1 into main is not the same as merging main into fix1, as illustrated below.
fix1 into main]
Changes made in d1 and e1 are available on main, but changes made in d are not available on fix1.
main into fix1]
Changes made in d are available on fix1, but changes made in d1 and e1 are not available on main.
Scenario You have a repo with two unmerged branches main and feature1.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m0"
commit id: "m1"
commit id: "m2"
branch feature1
commit id: "f1"
commit id: "[feature1] f2"
checkout main
commit id: "[HEAD → main] m3"
checkout feature1
Target Merge each branch into the other.
Preparation
Option 1: Create a fresh sandbox using the Git-Mastery app
- i.Navigate inside the
gitmastery-exercisesfolder. - ii.Run the
gitmastery download hp-merge-commitcommand.
The sandbox will be set up inside the gitmastery-exercises/hp-merge-commit folder.
Option 2: Repurpose the sandbox from the previous hands-on practical
You can continue with the earlier sports repo, which should match the revision graph in the Scenario above. For simplicity, ignore the feature1-alt branch.
1 Switch back to the feature1 branch.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m1"
commit id: "m2"
branch feature1
commit id: "f1"
commit id: "[HEAD → feature1] f2"
checkout main
commit id: "[main] m3"
checkout feature1
2 Merge the main branch into the feature1 branch, producing this result . Git creates a merge commit, shown as mc1 below.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m1"
commit id: "m2"
branch feature1
commit id: "f1"
commit id: "f2"
checkout main
commit id: "[main] m3"
checkout feature1
merge main id: "[HEAD → feature1] mc1"
git merge main
Right-click on the main branch and choose merge main into the current branch. Click OK in the next dialog.

If a confirmation dialog pops up, choose as follows:

The revision graph should now look like this (colors and line alignment might vary):

Observe that the changes from main (the imaginary bug fix in m3) are now available in feature1.
Running git diff HEAD^1 HEADExplanation of the command should show the changes from main introduced into feature1 (here, commit m3, the only new commit in main).
3 Add another commit to the feature1 branch by changing boxing.txt.
echo "Manny Pacquiao" >> boxing.txt
git commit -am "Add Manny to boxing.txt"
Switch to the main branch and add one more commit.
git switch main
echo "Lionel Messi" >> football.txt
git commit -am "Add Messi to football.txt"
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m1"
commit id: "m2"
branch feature1
commit id: "f1"
commit id: "f2"
checkout main
commit id: "m3"
checkout feature1
merge main id: "mc1"
commit id: "[feature1] f3"
checkout main
commit id: "[HEAD → main] m4"
4 Merge feature1 into the main branch, producing this result:
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m1"
commit id: "m2"
branch feature1
commit id: "f1"
commit id: "f2"
checkout main
commit id: "m3"
checkout feature1
merge main id: "mc1"
commit id: "[feature1] f3"
checkout main
commit id: "m4"
merge feature1 id: "[HEAD → main] mc2"
git merge feature1
Right-click on the feature1 branch and choose Merge.... The resulting revision graph should look like this:
The changes you made in feature1 are now available in main.
done!

When the destination branch hasn't diverged -- meaning it has no new commits since the merge base commit -- Git can bring in the source branch's changes more directly.
In the example on the left, the main branch has not changed since the merge base commit (i.e., c).

Git can merge by moving the destination branch pointer forward to include the new commits in the source branch. This is called a fast-forward merge because Git "fast-forwards" the branch ref to the tip of the other branch.
The example on the left shows how the main branch ref is moved during a fast-forward merge of the fix1 branch into the main branch.

After the fast-forward merge, the revision graph looks as if all changes were made directly on main. Git no longer records where fix1 previously diverged from main.
One downside of a fast-forward merge is that the revision graph does not show when the branch was merged (as there is no merge commit). This can make the project history harder to understand.
Scenario You have a repo with an unmerged branch add-swimming. The main branch has not diverged from the add-swimming branch yet.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "more commits ..."
commit id: "[main] mc2"
branch add-swimming
commit id: "a1"
commit id: "[HEAD → add-swimming] a2"
Target Do a fast-forward merge of the add-swimming branch into the main branch.
Preparation
Option 1: Create a fresh sandbox using the Git-Mastery app
- i.Navigate inside the
gitmastery-exercisesfolder. - ii.Run the
gitmastery download hp-merge-ffcommand.
The sandbox will be set up inside the gitmastery-exercises/hp-merge-ff folder.
Option 2: Repurpose the sandbox from the previous hands-on practical
To continue with the same sports repo, create a branch called add-swimming and add some commits to it:
Switch to main, create and switch to the new branch, add swimming.txt, stage it, and commit it.
Then change swimming.txt and commit those changes.
Equivalent commands:
git switch main
git switch -c add-swimming
echo "Michael Phelps" > swimming.txt
git stage swimming.txt
git commit -m "Add swimming.txt"
echo "Ian Thorpe" >> swimming.txt
git commit -am "Add Thorpe to swimming.txt"
git switch main
Target Do a fast-forward merge of the add-swimming branch.
1 Ensure you are on the main branch.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "more commits ..."
commit id: "[HEAD → main] mc2"
branch add-swimming
commit id: "a1"
commit id: "[add-swimming] a2"
2 Merge the add-swimming branch into the main branch. Observe that there is no merge commit: the main branch ref (and HEAD) moved to the tip of add-swimming (a2), so both branches now point to a2.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main (and add-swimming)'}} }%%
commit id: "more commits ..."
commit id: "mc2"
commit id: "a1"
commit id: "[HEAD → main][add-swimming] a2"
done!
You can force Git to create a merge commit even if fast-forwarding is possible. This keeps branch-merge points visible in the revision graph.
To prevent Git from fast-forwarding, use the --no-ff switch when merging. Example:
git merge --no-ff add-swimming
Two other useful git merge options:
--ff-only: Merge only if a fast-forward merge is possible.--ff: Prefer a fast-forward merge, but allow a merge commit if fast-forwarding is not possible. This is Git's default behavior, so the option is useful only if the default has been changed.
Select the box shown below when you merge a branch:
Trigger the branch operation using the following menu button:

In the next dialog, tick the following option:
To permanently prevent fast-forwarding:
- Go to Sourcetree
Settings. - Navigate to the
Gitsection. - Select the box
Do not fast-forward when merging, always create commit.
A squash merge combines all changes from the source branch into a single commit on the destination branch. Use it when the source branch's commits would clutter history (e.g., many experimental commits).
fix1 into main]
fix1 into main]
In the example above, fix1 has been squash-merged into main, creating a single 'squashed' commit e from the commits in fix1. The 'squashed' commit is a regular commit with one parent, not a merge commit with two parents.
After a squash merge, you typically delete the source branch, so its individual commits no longer appear in the destination branch's main history (you'll learn how to delete branches in an upcoming lesson). The history stays linear because one regular commit replaces the source branch's work, with no second-parent link to that branch.
Here is a comparison of the three merge types covered here: regular merging with a merge commit, fast-forward merging, and squash merging.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "more commits ..."
commit id: "[HEAD → main] m1"
branch feature
checkout feature
commit id: "f1"
commit id: "[feature] f2"
checkout main
merge feature
with a merge commit]
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "more commits ..."
commit id: "m1"
commit id: "f1"
commit id: "[HEAD → main][feature] f2"
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "more commits ..."
commit id: "m1"
commit id: "[HEAD → main] s1 (same as f1+f2)"
deleting the source branch]
The detour below covers the mechanics of a squash merge.
DETOUR: Undoing a Merge
- Ensure you are in the .
- Hard-reset that branch to its , rewinding the branch to its pre-merge state.
In the example below, you merged main into feature1.

If you want to undo that merge,
- Ensure you are in the
feature1branch (because that's the destination branch). - Reset
feature1to the commit that was its tip just before you mergedmaininto it.
Use this reset-based undo only for local/unshared merges; if the merge has been pushed/shared, prefer reverting to avoid rewriting shared history.
DETOUR: Comparing Branches
Comparing branches in Git shows how two lines of development differ. For example, before merging a branch, review the changes it would introduce to the main branch.
Two common ways to compare branches:
- Double-dot notation
git diff branchA..branchBcompares the latest snapshots of the two branches (branchAvsbranchB). This is the more common notation. - Triple-dot notation
git diff branchA...branchBshows the changes introduced inbranchBsince it diverged frombranchA. Git compares the two branches' merge base with the tip ofbranchB.
DETOUR: Doing a Squash Merge
To squash merge, use the --squash switch. It prepares a regular commit with the squashed changes, but stops before finalizing it.
git merge --squash feature-1
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested
Then make the commit yourself with your chosen commit message.
When merging branches, you need to tell Git how to resolve conflicting changes in different branches.
A conflict occurs when Git cannot automatically reconcile different changes made to the same part of a file.
A merge conflict happens when Git can't automatically combine branches because both branches changed the same part of a file in different ways. When this happens, Git pauses the merge and marks the conflicting sections in the affected files so you can resolve them yourself. Once you've resolved the conflicts, you can tell Git to continue the merge.
Scenario In the nouns repo (revision graph shown below), both main and fix1 modify the same location in the same file. main inserts black where fix1 inserts green.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "Add colours.txt"
branch fix1
checkout fix1
commit id: "[fix1] Add green, red, white"
checkout main
commit id: "[HEAD → main] Add black, red, white"
blue
black
red
white
main branch]
blue
green
red
white
fix1 branch]
Target Merge the two branches and reconcile their conflicting changes.
Preparation
Option 1: Create a fresh sandbox using the Git-Mastery app
- i.Navigate inside the
gitmastery-exercisesfolder. - ii.Run the
gitmastery download hp-merge-conflictscommand.
The sandbox will be set up inside the gitmastery-exercises/hp-merge-conflicts folder.
Option 2: Manually set up a sandbox
Create a repo with two branches containing conflicting changes, as follows:
- Create a repo named
nounswith one commit. - Start a branch named
fix1in the repo. Create a commit that adds a line of text to one of the files. - Switch back to
main. Create a commit with a conflicting change that adds different text in that exact location.
You can do this with the following commands:
mkdir nouns
cd nouns
git init -b main
echo "blue" > colours.txt
git stage colours.txt
git commit -m "Add colours.txt"
git switch -c fix1
echo -e "green\nred\nwhite" >> colours.txt
git commit -am "Add green, red, white"
git switch main
echo -e "black\nred\nwhite" >> colours.txt
git commit -am "Add black, red, white"
1 Try to merge the fix1 branch into the main branch. Git will pause the merge and report a merge conflict. If you open the conflicted file colours.txt, you will see something like this:
blue
<<<<<<< HEAD
black
=======
green
>>>>>>> fix1
red
white
2 Observe how the conflicted part is marked between a line starting with <<<<<<< and a line starting with >>>>>>>, separated by another line starting with =======.
The yellow highlight below shows the conflicting part from main. The HEAD label on line 2 means this conflicting change comes from the currently active branch, main:
blue
<<<<<<< HEAD
black
=======
green
>>>>>>> fix1
red
Similarly, this is the conflicting part that comes from the fix1 branch:
blue
<<<<<<< HEAD
black
=======
green
>>>>>>> fix1
red
3 Resolve the conflict by editing the file. Assume you want the merged version to keep both lines. Remove the conflict-marker lines and keep black and green:
blue
black
green
red
white
General steps for resolving a conflict:
- Remove conflict markers (e.g.,
<<<<<<< HEAD) - Edit the remaining text as needed (e.g., keep, edit, or delete one or both lines; you can also insert new text).
If there are multiple conflicts (in multiple files or different locations within the same file), resolve them the same way.
4 Stage the changes.
5 Complete the merge by doing one of the following:
- Option 1: Commit the staged changes (as you normally would).
- Option 2: Ask Git to resume the merge using the command
git merge --continue.
done!
Branches can be renamed, for example, to fix a mistake in the branch name.
Local branches can be renamed easily. Renaming a branch changes the branch reference (i.e., the name used to identify the branch). This is a cosmetic change: the commits, file contents, and merge relationships stay the same; only the name pointing to the branch tip changes.
Target You want to rename fantasy (not yet merged) and textbooks (merged), as shown below:
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m1"
branch textbooks
checkout textbooks
commit id: "[textbooks] t1"
checkout main
branch fantasy
checkout fantasy
commit id: "[fantasy] f1"
checkout main
merge textbooks id: "[HEAD → main] mc1"
→
[rename branches]
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m1"
branch study-books
checkout study-books
commit id: "[study-books] t1"
checkout main
branch fantasy-books
checkout fantasy-books
commit id: "[fantasy-books] f1"
checkout main
merge study-books id: "[HEAD → main] mc1"
Preparation
Option 1: Create a fresh sandbox using the Git-Mastery app
- i.Navigate inside the
gitmastery-exercisesfolder. - ii.Run the
gitmastery download hp-branch-renamecommand.
The sandbox will be set up inside the gitmastery-exercises/hp-branch-rename folder.
Option 2: Manually set up a sandbox
To create the repo samplerepo-books used in this hands-on practical, run the following commands in your terminal.
mkdir samplerepo-books
cd samplerepo-books
git init -b main
echo "Horror Stories" >> horror.txt
git add .
git commit -m "Add horror.txt"
git switch -c textbooks
echo "Textbooks" >> textbooks.txt
git add .
git commit -m "Add textbooks.txt"
git switch main
git switch -c fantasy
echo "Fantasy Books" >> fantasy.txt
git add .
git commit -m "Add fantasy.txt"
git switch main
sleep 1
git merge --no-ff -m "Merge branch textbooks" textbooks
The sleep 1 in line 17 adds a delay so the next commit has a different timestamp from the previous one.
Reason: Commit timestamps are rounded to the nearest second. If multiple commits have the same timestamp, git log output can look slightly different from what we expect because git log orders commits by commit timestamp.
steps:
To rename a branch, use the git branch -m <current-name> <new-name> command (-m stands for 'move'):
git branch -m fantasy fantasy-books
git branch -m textbooks study-books
git log --oneline --decorate --graph --all # verify the changes
* 443132a (HEAD -> main) Merge branch textbooks
|\
| * 4969163 (study-books) Add textbooks.txt
|/
| * 0586ee1 (fantasy-books) Add fantasy.txt
|/
* 7f28f0e Add horror.txt
Note these additional switches to the git log command:
--all: Shows all branches, not just the current branch.--graph: Shows a graph-like visualization.*indicates a commit, and vertical lines indicate branches.
Right-click the branch name and choose Rename.... Provide the new branch name in the next dialog.

done!
SIDEBAR: Branch naming conventions
Branch names can contain lowercase letters, numbers, /, dashes (-), underscores (_), and dots (.).
You can also use uppercase letters, but many teams avoid them for consistency.
A common branch naming convention is to prefix branch names with <category>/. Some examples:
feature/login-form— for new features (origin/feature/login-formcould be the matching remote-tracking branch)bugfix/profile-photo— for fixing bugshotfix/payment-crash— for urgent production fixesrelease/2.0— for preparing a releaseexperiment/ai-chatbot— for “just trying stuff”
Although a forward slash (/) in the prefix doesn't create folders in Git, some tools treat it like a path, which lets you group related branches when you run git branch. The example below shows how Sourcetree groups branches with the same prefix.

Branches can be deleted to get rid of them when they are no longer needed.
Deleting a branch deletes the corresponding branch ref from the revision history (it does not delete any commits). The impact of the loss of the branch ref depends on whether the branch has been merged.
When you delete a branch that has been merged, the commits of the branch will remain in the history and be safe. Only the branch ref is lost.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m1"
branch bug-fix
checkout bug-fix
commit id: "[bug-fix] b1"
checkout main
merge bug-fix id: "[HEAD → main] mc1"
→
[delete branch bug-fix]
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m1"
branch _
checkout _
commit id: "b1"
checkout main
merge _ id: "[HEAD → main] mc1"
In the above example, the deletion only removes the branch ref bug-fix. All commits remain reachable via the main branch, and the revision history is otherwise unchanged.
In fact, some prefer to delete the branch soon after merging it, to reduce clutter from branch references in the revision history.
When you delete a branch that has not been merged, the loss of the branch ref can render some commits unreachable (you may still be able to inspect or recover them for a while if you know their commit ID), putting them at risk of being lost eventually.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "[HEAD → main] m1"
branch bug-fix
checkout bug-fix
commit id: "[bug-fix] b1"
checkout main
→
[delete branch bug-fix]
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "[HEAD → main] m1"
branch _
checkout _
commit id: "b1"
checkout main
In the above example, the commit b1 is no longer reachable .
SIDEBAR: What makes a commit 'unreachable'?
Recall that a commit only has a pointer to its parent commit (not its descendant commits).
A commit is considered reachable if you can get to it by starting at a branch, tag, or other ref and walking backward through its parent commits. 'Reachable' is the normal state for commits — they are part of the visible history of a branch or tag.
If no branch, tag, or ref in the repo can be used as the starting point to reach a certain commit, that commit is unreachable. This often happens when you delete a branch or rewrite history (e.g., with reset or rebase), leaving some commits "orphaned" (or "dangling") without a ref pointing to them.
In the example below, C4 is unreachable (i.e., cannot be reached by starting at any of the three refs: v1.0 or main or ←HEAD), but the other three are all reachable.
Unreachable commits are not deleted immediately — Git keeps them for a while before cleaning them up. By default, Git retains unreachable commits for at least 30 days, during which they can still be recovered if you know their SHA. After that, they will be garbage-collected and lost for good.
Scenario You have the following repo, named samplerepo-books-2.
gitGraph BT:
%%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'main'}} }%%
commit id: "m1"
branch textbooks
checkout textbooks
commit id: "[textbooks] t1"
checkout main
branch fantasy
checkout fantasy
commit id: "[fantasy] f1"
checkout main
merge textbooks id: "[HEAD → main] mc1"
The work in the textbooks branch has been completed, and the branch has been merged, so there is no need to keep that branch anymore.
The work in the fantasy branch is no longer needed, so there is no need for that branch either.
Target Delete the textbooks (merged) and fantasy branches (unmerged).
Preparation
Option 1: Create a fresh sandbox using the Git-Mastery app
- i.Navigate inside the
gitmastery-exercisesfolder. - ii.Run the
gitmastery download hp-branch-deletecommand.
The sandbox will be set up inside the gitmastery-exercises/hp-branch-delete folder.
Option 2: Manually set up a sandbox
To create the repo samplerepo-books-2 manually, run the following commands in your terminal.
mkdir samplerepo-books-2
cd samplerepo-books-2
git init -b main
echo "Horror Stories" >> horror.txt
git add .
git commit -m "Add horror.txt"
git switch -c textbooks
echo "Textbooks" >> textbooks.txt
git add .
git commit -m "Add textbooks.txt"
git switch main
git switch -c fantasy
echo "Fantasy Books" >> fantasy.txt
git add .
git commit -m "Add fantasy.txt"
git switch main
sleep 1
git merge --no-ff -m "Merge branch textbooks" textbooks
1 Delete the (merged) textbooks branch.
Use the git branch -d <branch> command to delete a local branch safely. This command will fail if the branch has unmerged commits. In this case, it will succeed because the branch has no unmerged commits.
git branch -d textbooks
git log --oneline --decorate --graph --all # check the current revision graph
* 443132a (HEAD -> main) Merge branch textbooks
|\
| * 4969163 Add textbooks.txt
|/
| * 0586ee1 (fantasy) Add fantasy.txt
|/
* 7f28f0e Add horror.txt
Right-click on the branch name and choose Delete <branch>:

In the next dialog, click OK:

Observe that all commits remain. The only thing missing is the textbooks ref.
2 Make a copy of the SHA of the tip of the (unmerged) fantasy branch.
3 Delete the fantasy branch.
Attempt to delete the branch. It should fail, as shown below:
git branch -d fantasy
error: the branch 'fantasy' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D fantasy'
As the error message suggests, you can replace -d with -D to force the deletion.
git branch -D fantasy
Now, check the revision graph:
git log --oneline --decorate --graph --all
* 443132a (HEAD -> main) Merge branch textbooks
|\
| * 4969163 Add textbooks.txt
|/
* 7f28f0e Add horror.txt
Attempt to delete the branch as you did before. It will fail because the branch has unmerged commits.

Try again, but this time select the Force delete option, which will force Git to delete the unmerged branch:

Observe how the branch ref fantasy is gone, and the branch's unmerged commits are no longer visible in the commit graph (e.g., if you run git log --all).
4 Attempt to view the 'unreachable' commit whose SHA you noted in step 2.
For example, git show 32b34fb (use the SHA you copied earlier)
Observe how the commit still exists, and you are still able to inspect it using its commit ID (for now).
done!
Git worktrees let one local repository have multiple working directories at the same time.
At times, you need to have more than one branch checked out simultaneously. For example, while tests are running on your feature branch, you might need to quickly fix an urgent bug in another branch. Switching branches alters files in the project directory that the running tests might depend on. Another modern use case is coordinating multiple AI agents to work on different tasks in parallel, with each agent working on its own branch.
Git's worktree feature lets you create additional working directories attached to the same local repository. This lets you keep different branches active in separate directories. Unlike copying the repo or cloning it again, worktrees are lightweight and share the same underlying repository data, which makes it easier to bring changes from one worktree to another.
SIDEBAR: Working Directory vs Working Tree vs Worktree
- A working directory is the folder on your computer that contains your repo files. This is a filesystem idea: it is the directory you see in your editor, terminal, or file explorer. In a regular Git repository, the hidden
.gitdirectory is stored inside the top-level working directory and contains Git’s repository metadata . - A working tree is Git’s view of the checked-out project files in the working directory, excluding Git’s own metadata. When Git reports modified, deleted, or untracked files, it is inspecting the working tree and comparing it with the staging area and
HEAD. - A worktree is a working directory managed by Git's worktree feature.
The terms overlap, but they emphasize different perspectives: filesystem location, Git’s model of checked-out files, and Git’s feature for managing linked working directories.
The three terms are often used interchangeably, though, and the context usually makes it clear which one is meant.
Normally, a local repository starts with one worktree checked out in one working directory. When you switch branches, Git updates the files in that working directory so that they match the branch you switched to.
A linked worktree is an additional checkout that is connected to the same local repository and has its own working directory, staging area, and current HEAD, while sharing the same underlying repository data. Implications:
- Each worktree can be on a different branch, because each has its own
HEAD. - Changes made in one worktree do not appear as uncommitted changes in other worktrees, because each worktree has its own checked-out files.
- Commits and branch refs updated in one worktree are immediately visible in commands such as
git log --allfrom other worktrees, but each worktree’s checked-out files remain separate.
Git generally allows a branch to be checked out in only one worktree at a time. This prevents two worktrees from trying to move the same branch ref independently. In practice, you usually give each active worktree its own branch, or use a worktree temporarily to inspect a specific point in history.
Preparation
Create a study-notes repo as follows:
mkdir study-notes
cd study-notes
git init -b main
echo "# Study Notes" > README.md
git add README.md
git commit -m "Add README"
1 Add a linked worktree for a science branch. The git worktree add -b <branch> <path> command creates a new branch and checks it out in a new directory.
git worktree add -b science ../study-notes-science
In this case, Git creates the science branch in the ../study-notes-science directory.
A worktree can be located anywhere in the filesystem. One common convention is to create it in the same parent directory as the original working directory and name it <project name>-<branch name>, as shown above.
2 List the worktrees attached to the repo.
git worktree list
/.../study-notes 7947b49 [main]
/.../study-notes-science 7947b49 [science]
The output should show two worktrees: the original worktree and the linked science worktree.
3.1 Switch to the new worktree. To switch to a worktree, navigate to the directory where it is located.
cd ../study-notes-science
3.2 Do some work in the new worktree.
echo "# Science Notes" > science.md
git add science.md
git commit -m "Add science.md"
echo "Revise for science test" >> science.md
git status --short
These commands should create a new commit and leave some uncommitted changes in this worktree.
4.1 Switch back to the original worktree. Check the state.
cd ../study-notes
git log --oneline --decorate --graph --all
git status --short
* 54a709f (science) Add science.md
* 1587164 (HEAD -> main) Add README
The empty git status --short output shows that the uncommitted changes in the science worktree do not appear in the original worktree. The new commit you added in the science worktree appears in the git log output because the commit history is shared between worktrees.
4.2 Do some work in the original worktree.
echo -e "This is for keeping study notes" >> README.md
git commit -am "Add more info to README"
5 Switch back to the science worktree. Check the state.
cd ../study-notes-science
git status --short
git log --oneline --decorate --graph --all
Observe that the uncommitted changes you made earlier are still there. You can also see the new commit you made in the original worktree. This shows that the revision history is visible across linked worktrees.
6 Switch back to the original worktree. Attempt to remove the science worktree.
cd ../study-notes
git worktree remove ../study-notes-science
fatal: '../study-notes-science' contains modified or untracked files, use --force to delete it
Git refuses to remove the science worktree because it has uncommitted changes. This protects unsaved work from accidental deletion.
7 Go back to the science worktree. Commit the changes. Try to switch to main.
cd ../study-notes-science
git commit -am "Add more info to science.md"
While you are in the science worktree, first try to switch to main so that you can merge the science branch into it.
git switch main
fatal: 'main' is already used by worktree at '..../study-notes'
Git refuses to switch to main because it is already checked out in the original worktree.
8 Go back to the original worktree. Merge the science branch from there.
cd ../study-notes
git merge science
The merge should now succeed. Although the science branch was created in the science worktree, it can still be merged from another worktree.
9 Now you can remove the science worktree.
git worktree remove ../study-notes-science
Run git worktree list one last time. You should see only the original study-notes worktree.
git worktree list
Observe that the science branch is still there (e.g., run git branch --list) even though the worktree we created for it has been removed. You can delete the branch separately (e.g., run git branch -d science) if you wish.
If you delete a linked worktree folder manually instead of using git worktree remove, Git may keep a stale entry until you run git worktree prune.
done!
At this point: Now you can create, maintain, and merge multiple parallel branches in a local repo, and use worktrees to work with multiple branches in separate folders. This tour covered only the basic use of Git branches. More advanced usage will be covered in other tours.
What's next: Tour 7: Keeping Branches in Sync