Git Branches and Merging: A Complete Practical Guide

Branches allow developers to separate one line of work from another. A new feature can evolve without immediately changing stable code, an urgent fix can be prepared independently, and experiments can be abandoned without damaging the main development line.

Git makes branching lightweight because a branch is not a complete duplicated project directory. It is a movable reference to a commit. As new commits are created, the current branch reference moves forward.

This guide explains the branch model first, then turns it into a complete practical workflow. You will create and switch branches, inspect divergence, perform fast-forward and three-way merges, resolve conflicts, abort unsafe integrations, rename and delete branches, and verify whether work has been merged before cleanup.

What You Will Learn

  • What a Git branch actually represents.

  • How branches, commits, and HEAD are connected.

  • How to list, create, switch, rename, copy, and delete branches.

  • How to start a branch from another commit or branch.

  • How to compare branches before merging.

  • The difference between fast-forward and three-way merges.

  • How --ff-only, --no-ff, and --no-commit affect a merge.

  • How to understand and resolve merge conflicts.

  • How to abort or continue a merge safely.

  • How to inspect merged and unmerged branches before deletion.

  • How local branches differ from remote-tracking branches.

  • Common branching mistakes and practical team conventions.

What Is a Git Branch?

A branch is a named reference to a commit. It identifies the current tip of one line of development.

Assume a repository has three commits:

A---B---C  main

The branch named main points to commit C.

When you create a branch named feature/search, Git creates another reference at the same commit:

A---B---C  main
          feature/search

No files have been copied and no new commit has been created. Both names point to the same commit.

After switching to feature/search and creating a commit, only that branch moves:

A---B---C  main
         \
          D  feature/search

The branch is therefore a movable label, not a folder backup.

Branches Are Lightweight References

In Git, creating a branch is inexpensive because Git mainly creates a new reference. The project snapshots and commits already exist in the object database.

This makes short-lived branches practical for:

  • Features.

  • Bug fixes.

  • Experiments.

  • Documentation updates.

  • Release preparation.

  • Emergency hotfixes.

  • Refactoring.

Lightweight does not mean careless. A repository can become difficult to understand when branches are poorly named, kept indefinitely, or merged without review.

What Is HEAD?

HEAD identifies the location currently checked out in the working tree.

In a normal branch workflow, HEAD points to a branch, and the branch points to a commit:

HEAD -> feature/search -> D

When a new commit is created, the current branch moves to the new commit:

Before:
HEAD -> feature/search -> D

After commit E:
HEAD -> feature/search -> E

The other branches do not move automatically.

Create a Practice Repository

The examples use a small project named Task Notes.

macOS, Linux, or Git Bash

mkdir git-branches-demo
cd git-branches-demo
git init -b main

cat > app.txt <<'EOF'
Task Notes
Storage: local
EOF

cat > README.md <<'EOF'
# Task Notes

A small project for practicing Git branches and merges.
EOF

git add app.txt README.md
git commit -m "Create task notes project"

PowerShell

New-Item -ItemType Directory git-branches-demo
Set-Location git-branches-demo
git init -b main

@"
Task Notes
Storage: local
"@ | Set-Content app.txt

@"
# Task Notes

A small project for practicing Git branches and merges.
"@ | Set-Content README.md

git add app.txt, README.md
git commit -m "Create task notes project"

Verify the initial repository:

git status
git branch --show-current
git log --oneline --decorate

List Local Branches

Run:

git branch

The current branch is marked with an asterisk:

* main

Show the latest commit on each branch:

git branch -v

Show more relationship information, including upstream status when configured:

git branch -vv

List local and remote-tracking branches:

git branch -a

List only remote-tracking branches:

git branch -r

In this local practice repository, remote lists will remain empty until a remote is added and fetched.

Create a Branch Without Switching

Create a branch at the current commit:

git branch feature/search

List branches:

git branch

Expected structure:

  feature/search
* main

The new branch exists, but HEAD still points to main.

Confirm the graph:

git log --oneline --decorate --all

Both branch names should decorate the same commit.

Switch to an Existing Branch

Use:

git switch feature/search

Verify:

git branch --show-current
git status

Switching updates the working tree and index to match the selected branch, as long as doing so does not overwrite unsafe local changes.

The Older checkout Command

You will often see:

git checkout feature/search

git checkout can switch branches, restore paths, and detach HEAD. Modern Git provides git switch for branch switching and git restore for file restoration, which makes intent clearer.

Existing projects and scripts may still use checkout, so developers should recognize both forms.

Create and Switch in One Command

Create a new branch and switch to it:

git switch -c feature/export

The older equivalent is:

git checkout -b feature/export

The -c option creates the branch only if it does not already exist.

The force-create form is:

git switch -C feature/export

-C can reset an existing branch to a new starting point. Use it carefully because moving an existing branch reference may make commits unreachable from that branch.

Create a Branch from a Specific Starting Point

A new branch normally starts at the current HEAD. You can choose another starting point:

git switch -c hotfix/login main

Create a branch from a tag:

git switch -c support/v1.0 v1.0.0

Create a branch from a commit:

git switch -c investigation 7a93c2e

Create without switching:

git branch investigation 7a93c2e

The starting point determines the first commit reachable from the new branch tip.

Make the First Feature Commit

Switch to the search branch if necessary:

git switch feature/search

Create a feature file.

macOS, Linux, or Git Bash

cat > search.txt <<'EOF'
Search tasks by keyword.
EOF

git add search.txt
git commit -m "Add keyword search description"

PowerShell

"Search tasks by keyword." | Set-Content search.txt

git add search.txt
git commit -m "Add keyword search description"

Inspect the graph:

git log --oneline --graph --decorate --all

Conceptually:

A  main
 \
  B  feature/search

The feature branch moved forward. main remained at the original commit.

Switching with Uncommitted Changes

Git may allow a branch switch when local changes can be preserved safely. It refuses when switching would overwrite changes or create an unsafe state.

Before switching branches, run:

git status

Then choose one of these actions:

  • Commit a complete logical change.

  • Stash incomplete work when appropriate.

  • Discard unwanted changes deliberately.

  • Remain on the current branch until the work is organized.

Do not use force options merely to bypass a warning. Git may be protecting uncommitted work.

Switch Back to the Previous Branch

Use:

git switch -

This switches to the previously checked-out branch or location.

The equivalent revision notation is often represented internally by:

@{-1}

The shortcut is useful when moving repeatedly between a feature branch and its integration branch.

Detached HEAD

When you switch directly to a commit rather than a branch, HEAD can become detached:

git switch --detach HEAD~1

In this state, HEAD points directly to a commit:

HEAD -> B

No branch name automatically moves when you create a new commit.

Detached mode is useful for:

  • Inspecting an old version.

  • Running tests against a historical commit.

  • Temporarily exploring another point in history.

If you create valuable commits while detached, preserve them by creating a branch:

git switch -c recovery/experiment

To return without preserving new detached commits:

git switch main

Detached HEAD is not inherently an error, but commits created there can become difficult to find after leaving unless a branch or tag preserves them.

Rename a Branch

Rename the current branch:

git branch -m feature/task-search

Rename another local branch:

git branch -m old-name new-name

Force a rename when the target name already exists:

git branch -M new-name

Use force carefully. It can replace the destination branch reference.

A local rename does not automatically rename a branch on a remote hosting platform. Remote branch publication and cleanup require separate push operations, which will be covered in the remote collaboration articles.

Copy a Branch Reference

Git can copy a branch and its configuration:

git branch -c feature/task-search backup/task-search

Force-copy with:

git branch -C feature/task-search backup/task-search

Copying a branch name is less common than creating a normal branch at the same commit. It is useful when you intentionally want related branch configuration and reflog behavior copied as well.

Compare Branches Before Merging

Never merge only because a branch name sounds complete. Inspect its commits and content.

Show Commits Unique to the Feature Branch

git log --oneline main..feature/task-search

Show Commits Unique to Main

git log --oneline feature/task-search..main

Show Unique Commits on Both Sides

git log --left-right --graph --oneline main...feature/task-search

Compare Feature Changes Since Divergence

git diff --stat main...feature/task-search
git diff main...feature/task-search

Show the Common Ancestor

git merge-base main feature/task-search

The merge base is a best common ancestor used to reason about changes made after the branches diverged.

What Does git merge Do?

git merge incorporates changes from another commit or branch into the current branch.

The direction is crucial.

To merge the search feature into main:

git switch main
git merge feature/task-search

The current branch receives the other branch’s changes. Running the command while on the feature branch would attempt to merge main into the feature branch instead.

Before every merge, confirm:

git branch --show-current
git status

Fast-Forward Merge

A fast-forward is possible when the current branch has no new commits after the feature branch diverged.

Before:

A---B  main
     \
      C---D  feature

More precisely, main points to B, and the feature branch is a direct descendant:

A---B---C---D  feature
     ^
     main

After:

A---B---C---D  main, feature

Git only moves the main reference forward. It does not need a merge commit because there is no divergent current-branch history to combine.

Perform the merge:

git switch main
git merge feature/task-search

The result may report a fast-forward update.

Require Fast-Forward Only

Use:

git merge --ff-only feature/task-search

This succeeds only when Git can update the current branch without creating a merge commit.

If the histories diverged, the command stops instead of choosing a merge strategy.

This is useful when a workflow requires linear integration and wants unexpected divergence to be handled explicitly.

Force a Merge Commit with --no-ff

Even when fast-forwarding is possible, you can request a merge commit:

git merge --no-ff feature/task-search

The history becomes:

A---B-------M  main
     \     /
      C---D    feature

The merge commit preserves a visible integration boundary around the feature branch.

Benefits may include:

  • Making a completed feature visible as one grouped branch in history.

  • Providing a merge message with integration context.

  • Making a whole feature easier to identify for later reversion.

Costs may include:

  • Additional merge commits.

  • A more complex graph.

  • Noise when every tiny branch receives a merge commit.

The correct choice depends on the team’s history policy.

Three-Way Merge

A three-way merge is needed when both branches contain commits after their common ancestor.

Example:

          C---D  feature
         /
A---B---E---F    main

Git considers three important snapshots:

  1. The merge base where the branches shared history.

  2. The current branch tip.

  3. The branch being merged.

If changes can be combined automatically, Git creates a merge result and normally records a merge commit:

          C---D
         /     \
A---B---E---F---M  main

The merge commit has two parents: the previous current-branch tip and the merged branch tip.

Create a Three-Way Merge Example

Starting from a clean main, create a reporting branch:

git switch -c feature/report

Add a report file:

macOS, Linux, or Git Bash

cat > report.txt <<'EOF'
Report completed tasks.
EOF

git add report.txt
git commit -m "Add completed-task report description"

PowerShell

"Report completed tasks." | Set-Content report.txt

git add report.txt
git commit -m "Add completed-task report description"

Switch to main and make an independent change:

git switch main

macOS, Linux, or Git Bash

cat >> README.md <<'EOF'

## Storage

Tasks are stored locally.
EOF

git add README.md
git commit -m "Document local task storage"

PowerShell

@"

## Storage

Tasks are stored locally.
"@ | Add-Content README.md

git add README.md
git commit -m "Document local task storage"

The branches now diverge. Merge the feature:

git merge feature/report

Because the changes affect separate files, Git should combine them automatically and create a merge commit.

Inspect the result:

git log --oneline --graph --decorate --all
git show --stat --summary HEAD

Inspect a Merge Before Committing

Use:

git merge --no-commit --no-ff feature/report

This performs the merge but stops before creating the merge commit, allowing you to inspect and adjust the result.

Review:

git status
git diff --staged
git diff --check

Run tests, then commit:

git commit

Why include --no-ff? A fast-forward update does not create a merge commit, so --no-commit cannot stop before a merge commit that does not exist. Combining the options ensures the integration stops for review when a merge commit is intended.

Preview Whether a Merge Base Exists

Inspect the common ancestor:

git merge-base main feature/report

Check whether one branch is already an ancestor of another:

git merge-base --is-ancestor feature/report main

The command communicates through its exit status:

  • Success means the first revision is an ancestor of the second.

  • A nonzero status means it is not.

This is useful in scripts and release checks.

What Is a Merge Conflict?

A merge conflict occurs when Git cannot determine a safe combined result automatically.

Common causes include:

  • Both branches changed the same lines differently.

  • One branch deleted a file that the other modified.

  • Both branches added different files at the same path.

  • A file or directory was renamed in incompatible ways.

  • Binary files changed on both branches.

A conflict is not corruption. Git has preserved the competing information and asks a developer to decide the intended result.

Create a Controlled Merge Conflict

Start from a clean main. Create a branch:

git switch -c feature/cloud-storage

Change app.txt to:

Task Notes
Storage: cloud

Stage and commit:

git add app.txt
git commit -m "Use cloud task storage"

Return to main:

git switch main

Change the same line differently:

Task Notes
Storage: encrypted local database

Commit:

git add app.txt
git commit -m "Use encrypted local task storage"

Now merge:

git merge feature/cloud-storage

Git should stop and report a conflict in app.txt.

Read the Conflict State

Run:

git status

Git identifies unmerged paths and provides commands for continuing or aborting.

Short status may show:

UU app.txt

UU means both sides modified the file and the conflict remains unresolved.

Other conflict codes can represent delete/modify, add/add, or other combinations.

Understand Conflict Markers

Open the conflicted file. A text conflict may look like:

Task Notes
<<<<<<< HEAD
Storage: encrypted local database
=======
Storage: cloud
>>>>>>> feature/cloud-storage

The sections mean:

  • <<<<<<< HEAD: Content from the current branch.

  • =======: Separator.

  • >>>>>>> feature/cloud-storage: Content from the branch being merged.

The final file must contain the actual intended content, not the conflict markers.

Resolve a Conflict Manually

Suppose the correct product decision is to support both modes. Edit the file to:

Task Notes
Storage: encrypted local database with optional cloud synchronization

Then mark the file resolved by staging it:

git add app.txt

Check for unresolved files:

git status
git diff --check

Review the combined result:

git diff --staged

Complete the merge:

git commit

Or use:

git merge --continue

git merge --continue completes the merge after conflicts are resolved and staged. Git may open the editor for the merge message.

Use the Index Stages During a Conflict

During an unresolved merge, the index can store multiple stages for a conflicted path:

  • Stage 1: Merge-base version.

  • Stage 2: Current branch version.

  • Stage 3: Other branch version.

Inspect them:

git ls-files -u

View the base version:

git show :1:app.txt

View the current side:

git show :2:app.txt

View the other side:

git show :3:app.txt

This can be more precise than reading conflict markers alone.

Ours and Theirs During a Normal Merge

During a normal merge:

  • Ours refers to the current branch checked out when the merge began.

  • Theirs refers to the branch or commit being merged.

Take the current side of one conflicted file:

git checkout --ours app.txt
git add app.txt

Take the incoming side:

git checkout --theirs app.txt
git add app.txt

Modern restoration syntax can also select index stages in supported workflows:

git restore --ours app.txt
git restore --theirs app.txt

Do not use these commands automatically. They replace the working file with one side and may discard valuable changes from the other. Review the result before staging.

Also note that the meaning of “ours” and “theirs” can become counterintuitive during rebasing because the operation’s perspective differs. Always verify the current operation and file content.

Use a Merge Tool

After configuring a supported visual or terminal merge tool, run:

git mergetool

A merge tool can display:

  • The common base.

  • The current branch version.

  • The incoming version.

  • The proposed result.

After the tool finishes, inspect the files and repository status. Some tools leave backup files that should be removed or ignored according to project policy.

Abort a Merge

When the merge should not continue:

git merge --abort

Git attempts to reconstruct the state before the merge began.

Abort is safest when the working tree was clean before starting the merge. Pre-existing uncommitted changes can make restoration more complicated.

This is why a clean working tree is strongly recommended before integration.

Fallback Seen in Older Workflows

You may encounter:

git reset --merge

It can be used in related recovery situations, but git merge --abort communicates the intention more clearly during an active merge.

Restart Conflict Resolution

If a conflict was edited incorrectly but the entire merge should not be aborted, restore the conflict presentation carefully.

One approach is to restore the unresolved stages for the file through the merge operation’s available index state, but behavior depends on what has already been staged or overwritten.

For beginners, the safest choices are usually:

  • Use version-control-aware editor actions before staging.

  • Inspect stages with git show :1:, :2:, and :3:.

  • Abort and restart the merge when the resolution process became unreliable.

Do not continue from a file whose origin you no longer understand.

Conflict Resolution Workflow

  1. Run git status.

  2. Identify every unmerged path.

  3. Open each conflict and understand both intentions.

  4. Consult tests, requirements, related commits, and teammates when necessary.

  5. Edit the file into the correct combined state.

  6. Remove all conflict markers.

  7. Run git add path to mark the path resolved.

  8. Repeat until no unmerged paths remain.

  9. Run tests and static checks.

  10. Review the staged result.

  11. Complete the merge with git merge --continue or git commit.

  12. Inspect the final graph and application behavior.

Inspect an In-Progress Merge

Useful commands include:

git status
git diff
git diff --staged
git diff --check
git ls-files -u

During conflicts:

  • git diff displays unresolved combined diff information.

  • git diff --staged shows resolved content already prepared for the merge commit.

  • git ls-files -u lists unmerged index entries.

  • git diff --check helps detect whitespace errors and remaining conflict markers in added lines.

Merge Messages

When a merge commit is created, Git proposes a message. You can supply one:

git merge --no-ff feature/report -m "Merge completed-task reporting"

Or allow the editor to open:

git merge --no-ff --edit feature/report

A useful merge message can explain:

  • Which feature or fix was integrated.

  • Why a non-obvious conflict was resolved in a particular way.

  • Important compatibility or deployment considerations.

  • Related issue or review references according to team conventions.

Do not use a merge message as a substitute for clear commits and review history.

Merge Strategy and Strategy Options

Git supports merge strategies selected with -s and strategy-specific options passed with -X.

For ordinary two-head merges, Git’s default strategy is designed for common branch integration and handles renames and many automatic combinations.

Examples you may encounter:

git merge -s ort feature/report
git merge -Xours feature/report
git merge -Xtheirs feature/report

The -Xours and -Xtheirs strategy options are not the same as discarding the entire other branch or current branch. They influence how conflicting hunks are favored during the merge while still incorporating non-conflicting changes.

Do not use strategy options to silence conflicts unless the policy is intentional and the result is tested.

The ours Strategy Is Different

This command:

git merge -s ours obsolete-branch

creates a merge result that uses the current tree while recording the other history as merged.

It is fundamentally different from -Xours. The ours strategy ignores the other tree’s content for the result. This is an advanced history operation and should not be used as a routine conflict shortcut.

Merge More Than One Branch

Git can perform an octopus merge with multiple heads in suitable non-conflicting cases:

git merge branch-a branch-b branch-c

This creates a merge with more than two parents when the strategy can resolve it automatically.

Octopus merges are generally used for combining several independent, already-tested topics. They are not suitable for complex conflict resolution because understanding a many-parent conflict becomes difficult.

Find Branches Already Merged

List local branches whose tips are reachable from HEAD:

git branch --merged

List branches not fully merged into HEAD:

git branch --no-merged

Check relative to a specific branch:

git branch --merged main
git branch --no-merged main

This is useful before branch cleanup.

A branch appearing under --merged main means its tip is reachable from main. It does not prove that the feature was integrated through a merge commit; fast-forwarding, rebasing, or equivalent changes can affect interpretation.

Find Branches Containing a Commit

List branches containing a selected commit:

git branch --contains <commit>

List branches that do not contain it:

git branch --no-contains <commit>

This helps answer questions such as:

  • Has the security fix reached the release branch?

  • Which maintenance branches contain this patch?

  • Can this temporary branch be removed without losing its tip?

Delete a Local Branch Safely

Delete a branch only when you are not currently on it:

git branch -d feature/report

The lowercase -d performs a safety check and normally refuses to delete a branch that is not fully merged into an appropriate upstream or current history.

Force deletion:

git branch -D feature/report

-D is equivalent to a forceful delete. It removes the branch reference even when commits may not be reachable elsewhere.

Before forcing:

git log --oneline main..feature/report
git branch --contains feature/report
git reflog show feature/report

If the work might still matter, create a backup tag or branch rather than deleting blindly.

Deleting a Branch Does Not Delete Commits Immediately

A branch name is a reference. Deleting it removes that reference, not necessarily the underlying commit objects immediately.

Commits remain available while reachable from another branch, tag, remote-tracking reference, or other reference.

Recently deleted local branch tips may also remain discoverable through reflogs for a limited period. However, unreachable objects can eventually be pruned.

Reflog is a recovery aid, not a permanent branch archive.

Local Branches and Remote-Tracking Branches

A local branch is a branch you can check out and commit on, such as:

main
feature/search

A remote-tracking branch records Git’s latest known state of a branch from a remote after fetching:

origin/main
origin/feature/search

You do not normally commit directly on origin/main. It moves when remote information is fetched.

Create a local branch based on a remote-tracking branch:

git switch -c feature/search --track origin/feature/search

In many common cases, Git can infer the local name:

git switch feature/search

Remote publishing, upstream configuration, pushing, fetching, and deleting remote branches will be covered separately because they involve collaboration and server state.

What Is an Upstream Branch?

A local branch can be configured to track an upstream branch. This relationship helps commands report whether the local branch is ahead or behind and provides defaults for operations such as pull and push, depending on configuration.

Inspect tracking information:

git branch -vv

Set an upstream for an existing local branch:

git branch --set-upstream-to=origin/feature/search feature/search

Remove it:

git branch --unset-upstream feature/search

An upstream is not the same as a parent commit or merge base. It is branch configuration describing a related remote or local branch.

Merge vs Rebase at a High Level

Merging combines histories and can create a commit with multiple parents. Rebasing replays commits onto a new base and creates replacement commits.

Merge:

          C---D
         /     \
A---B---E---F---M

Rebase conceptually:

A---B---E---F---C'---D'

Rebasing can produce a linear history, but it rewrites commit identifiers. Merging preserves the existing commits and records integration explicitly.

This article focuses on merging. Rebasing should be learned separately because conflict perspective, shared-history safety, and recovery differ.

Branch Naming Conventions

Useful names describe purpose:

feature/task-search
fix/login-timeout
hotfix/payment-callback
docs/local-setup
refactor/order-service
release/2.4

Avoid names such as:

test
new
final
branch2
john-work
changes

Good conventions are:

  • Short enough to type and read.

  • Specific enough to identify purpose.

  • Consistent across the team.

  • Compatible with repository and automation rules.

  • Free of private or sensitive information.

Some teams include ticket identifiers:

feature/APP-142-task-search

The exact style matters less than consistency and clarity.

Practical Feature Branch Workflow

1. Start from an Updated Integration Branch

git switch main
git status

When remotes are involved, fetch and update according to the team workflow before creating the branch.

2. Create the Feature Branch

git switch -c feature/task-search

3. Make Focused Commits

git add -p
git diff --staged
git commit -m "Add task keyword matching"

4. Inspect the Feature

git log --oneline main..feature/task-search
git diff main...feature/task-search

5. Run Tests

# Use the project’s real test command
php vendor/bin/phpunit

6. Merge into the Target Branch

git switch main
git merge --no-ff feature/task-search

Use the merge mode required by the team. Some teams prefer fast-forward-only integration; others preserve merge commits.

7. Verify the Integrated Result

git status
git log --oneline --graph --decorate --all

Run the full relevant test suite on the merged result, not only on the feature branch.

8. Delete the Completed Local Branch

git branch -d feature/task-search

Practical Hotfix Workflow

Assume a feature branch is incomplete when an urgent production issue appears.

1. Preserve Incomplete Feature Work

Commit a coherent checkpoint or stash it according to team practice.

2. Switch to the Production Base

git switch main

3. Create a Hotfix Branch

git switch -c hotfix/login-timeout

4. Implement and Test the Fix

git add -p
git commit -m "Increase login provider timeout"

5. Integrate the Fix

git switch main
git merge --no-ff hotfix/login-timeout

6. Bring the Fix into Other Active Lines

Depending on the repository workflow, merge the updated base into the feature branch or apply the fix through another approved integration method.

git switch feature/large-change
git merge main

This prevents the active feature from reintroducing old behavior later.

Manual Run: Fast-Forward Merge

Initial History

A  main

Create a Branch

git switch -c feature/a

Create Two Commits

A---B---C  feature/a
^
main

Return to Main

git switch main

Merge

git merge feature/a

Result

A---B---C  main, feature/a

No merge commit is needed. The current branch pointer moves to C.

Manual Run: Three-Way Merge

Initial History

A---B  main

Feature Work

     C---D  feature
    /
A---B      main

Independent Main Work

     C---D  feature
    /
A---B---E---F  main

Merge While on Main

git switch main
git merge feature

Result

     C---D
    /     \
A---B---E---F---M  main

The new merge commit M has the old main tip and the feature tip as parents.

Manual Run: Conflict and Resolution

Base File

Storage: local

Main Changes It To

Storage: encrypted local database

Feature Changes It To

Storage: cloud

Merge Attempt

git switch main
git merge feature/cloud-storage

Conflict State

<<<<<<< HEAD
Storage: encrypted local database
=======
Storage: cloud
>>>>>>> feature/cloud-storage

Resolved Product Decision

Storage: encrypted local database with optional cloud synchronization

Mark Resolved and Continue

git add app.txt
git diff --staged
git merge --continue

Verify

git status
git log --oneline --graph --decorate --all

Common Branching Mistakes

Creating a Branch from the Wrong Starting Point

Always inspect the current branch and recent history before creating a new branch.

Forgetting Which Branch Is Current

Run:

git branch --show-current
git status

before committing or merging.

Treating a Branch as a Folder Copy

A branch is a reference to history. Switching changes the working tree to match another history line.

Leaving Uncommitted Changes Before Merging

Pre-existing changes complicate conflict resolution and abort behavior.

Merging in the Wrong Direction

git merge other changes the current branch. Switch to the intended target first.

Resolving Conflicts by Keeping Only the Version That Compiles

A syntactically valid result can still violate requirements or silently remove behavior. Understand both changes and run tests.

Using ours or theirs Without Reviewing

Whole-file selection may discard valid work from one side.

Deleting an Unmerged Branch with -D

Force deletion can remove the easiest reference to unique commits.

Keeping Completed Branches Forever

Stale branches create noise and make it difficult to distinguish active work from historical leftovers.

Using --no-ff or --ff-only Without a Team Policy

Both choices shape history. Apply them intentionally and consistently.

Confusing a Local Branch with origin/branch

A remote-tracking branch records fetched remote state. It is not the normal local branch where you create commits.

Best Practices for Branches and Merges

  • Create branches from the correct, updated base.

  • Use descriptive and consistent branch names.

  • Keep one primary purpose per branch.

  • Create focused commits before integration.

  • Check the current branch before merging.

  • Start merges with a clean working tree.

  • Inspect unique commits and the three-dot diff before integration.

  • Run tests on both the feature and merged result.

  • Resolve conflicts based on intended behavior, not only text preference.

  • Review conflict resolutions before completing the merge.

  • Use force deletion only after proving unique work is no longer needed.

  • Delete completed short-lived branches to reduce clutter.

  • Document whether the team uses fast-forward, merge commits, squash merging, or rebasing.

Branch Command Reference

CommandPurpose
git branchList local branches
git branch -vvShow branch tips and upstream relationships
git branch nameCreate a branch without switching
git switch nameSwitch to an existing branch
git switch -c nameCreate and switch to a branch
git switch -Return to the previous checkout location
git switch --detach revisionInspect a revision with detached HEAD
git branch -m old newRename a local branch
git branch -d nameDelete a safely merged local branch
git branch -D nameForce-delete a local branch
git branch --merged mainList branches merged into main
git branch --no-merged mainList branches not merged into main
git branch --contains commitList branches containing a commit

Merge Command Reference

CommandPurpose
git merge branchMerge a branch into the current branch
git merge --ff-only branchAllow only a fast-forward update
git merge --no-ff branchCreate a merge commit even when fast-forwarding is possible
git merge --no-commit --no-ff branchPrepare a merge commit and stop for inspection
git merge --continueComplete a merge after conflicts are resolved
git merge --abortAttempt to return to the pre-merge state
git merge-base A BShow a best common ancestor
git mergetoolOpen the configured merge-resolution tool
git ls-files -uList unmerged index entries
git diff --checkCheck for whitespace errors and conflict markers in changes

Pre-Merge Checklist

  • The current branch is the intended merge target.

  • The working tree and index are clean.

  • The feature branch contains the intended commits.

  • The branch diff has been reviewed.

  • Relevant tests pass before integration.

  • The required merge mode is known: fast-forward, merge commit, or another approved method.

  • No unrelated local changes will interfere.

  • A recovery path exists for high-risk integration.

Post-Merge Checklist

  • No unresolved paths remain.

  • No conflict markers remain in tracked files.

  • The combined code passes tests and static checks.

  • The merge commit or fast-forward result is visible in history.

  • The application behaves correctly with both sets of changes.

  • The completed branch is safe to delete.

  • Other long-lived branches receive critical fixes when required.

Frequently Asked Questions

Does Creating a Branch Copy the Entire Project?

No. Git creates a lightweight reference to a commit. The working tree changes when you switch branches.

What Is the Difference Between git branch and git switch -c?

git branch name creates the branch without switching. git switch -c name creates it and checks it out.

Why Can Git Not Switch Branches?

Local changes may be overwritten by the target branch. Commit, stash, discard, or otherwise organize the changes safely before switching.

Which Branch Receives the Merge?

The currently checked-out branch receives the branch named in git merge.

What Is a Fast-Forward Merge?

It moves the current branch reference forward because the other branch is already a descendant and no divergent current-branch commits need combining.

Does --no-ff Always Create a Merge Commit?

It prevents fast-forwarding in a normal nontrivial branch merge and requests a merge commit. Other special cases and command conditions should be checked in the official documentation.

Why Did --no-commit Not Stop My Merge?

A fast-forward update creates no merge commit to pause before. Use --no-ff --no-commit when a reviewable merge commit is required.

Is a Merge Conflict a Git Error?

No. It means Git cannot safely choose the combined content automatically and requires a human decision.

Can I Abort a Conflict?

Yes, use git merge --abort. Restoration is most reliable when the working tree was clean before the merge.

Should I Always Choose Ours or Theirs?

No. Most real conflicts require a combined result. Selecting one whole side can discard necessary behavior.

Can I Delete a Branch After Merging?

Yes. Use git branch -d name after verifying the branch’s work is reachable from the intended target.

What Happens to Commits After Branch Deletion?

They remain while reachable from another reference. Unique unreachable commits may eventually be pruned, although local reflogs can provide temporary recovery opportunities.

What Is the Difference Between a Branch and an Upstream?

A branch is a movable reference to a commit. An upstream is configuration linking a local branch to another branch for status and default synchronization behavior.

Should I Merge or Rebase?

Both integrate lines of development differently. Merging preserves existing commits and can record integration with a merge commit. Rebasing creates replacement commits on a new base. Use the workflow defined by the team and avoid rewriting shared history casually.

Conclusion

Git branches are lightweight references that let development continue along independent lines. They make feature work, experiments, fixes, and releases easier to isolate without copying the project or immediately changing stable code.

Merging brings those lines together. A fast-forward moves a branch reference when no divergence exists. A three-way merge combines changes using the common ancestor and both branch tips. When Git cannot determine the correct combined content, it preserves the competing versions and requires a deliberate conflict resolution.

A safe integration workflow begins before the merge command: use a clean working tree, confirm the target branch, review unique commits and diffs, and run tests. During conflicts, understand both intentions, stage the correct combined result, and verify the final application rather than choosing one side automatically.

After integration, inspect the graph, confirm that the branch tip is reachable from the intended target, and remove completed short-lived branches. The result is not only working code but a history that future developers can understand.

The next article will focus on Git stash and tags: temporarily setting work aside, recovering stashed changes, creating release markers, comparing lightweight and annotated tags, and publishing tags safely.

Official References and Further Reading