WebDevNav
Technology

Git Merge — Bringing Your Work Together

Author

Naveen S

Date Published

You've built a feature on a separate branch. Now what? Learn how Git merges branches, when it creates a merge commit, why conflicts happen, and how to resolve them without losing your work.

Git Merge: Bringing Your Work Together

In the previous article, we learned that Git branches let us work on features independently.

We created a branch, made a commit, and switched back to main. The feature was still there, waiting for us.

But there's a question we haven't answered yet:

How do we bring that feature into main?

That's where git merge comes in.

Merging is how Git integrates changes from one line of development into another. Sometimes it's as simple as moving a branch pointer forward. Other times, Git needs to combine two histories—and occasionally, it needs your help deciding what the final code should look like.

Let's explore each case.

1. Start with two branches

Imagine we're building a web application.

Our repository begins with one commit:

A main

We create a feature branch:

git switch -c feature/login

Then we add a login page and commit the change:

git add login.html
git commit -m "Add login page"

Our history now looks like this:

A --- B feature/login
^
main

Commit B contains the login page. The feature/login branch points to B, while main still points to A.

Now the feature is ready to integrate.

We switch to the target branch:

git switch main

And merge the feature:

git merge feature/login

Git now needs to figure out how to integrate the feature branch into main.

The exact result depends on the history of the two branches.

Let's start with the simplest case.

2. Fast-forward merge: When Git can simply move forward

In our example, main hasn't changed since we created feature/login.

The feature branch is ahead, but main is still an ancestor of the feature's latest commit.

Before the merge:

A --- B feature/login
^
main

Git doesn't need to create a new commit to combine the histories. It can simply move main forward to B.

After the merge:

A --- B
^
main, feature/login

This is called a fast-forward merge.

Git advances the target branch pointer because the target branch can move forward along the existing history without combining divergent lines of development.

You can try it yourself:

git switch main
git merge feature/login

Git may report that the merge was a fast-forward.

Notice what didn't happen: Git didn't create a separate merge commit. The feature commit already exists, and main can simply point to it.

Why is this useful?

Fast-forward merges keep history linear and easy to read.

For a small feature developed while main remains unchanged, they're often exactly what you want.

However, real projects don't always stay that simple.

3. What if both branches have new commits?

Suppose another developer—or you—makes a change on main while the feature is being developed.

Our history now looks like this:

C main
/
A ---+
\
B feature/login

More conventionally:

C main
/
A --+
\
B feature/login

Both branches started from A, but they now contain different commits:

Commit B adds the login feature.

Commit C updates the application's homepage.

Neither branch is simply ahead of the other.

Git can't fast-forward main to B because doing so would leave C out of the target history.

Instead, Git must combine the changes.

This is where a three-way merge comes in.

4. Three-way merge: Combining independent histories

When branches have diverged, Git generally identifies three relevant commits:

The merge base: The common ancestor of the two branch tips.

The target branch tip: The current commit on the branch receiving the merge.

The source branch tip: The commit at the end of the branch being merged.

In our example:

C main
/
A ---+
\
B feature/login

A is the common ancestor. B and C represent the independent changes made since then.

Git uses these three points to determine how to combine the changes.

If the changes can be combined automatically, Git creates a merge result. By default, when a normal merge joins divergent histories, that result is recorded as a new merge commit with both branch tips as parents.

Conceptually:

C --------- M main
/ /
A ---+ /
\ /
B ------- feature/login

Here, M is the merge commit. It connects the two lines of development.

This is called a three-way merge, and the resulting commit is commonly called a merge commit.

The key idea is that Git is combining histories, not simply choosing one branch and discarding the other.

5. What does a merge commit actually record?

A regular commit generally has one parent, except for a root commit, which has none.

A merge commit usually has two parents: the tips of the two histories being joined.

That means the history records not only the resulting project snapshot but also how the two lines of development came together.

You can inspect this history with:

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

For a more compact view, you might see something like:

* 7d9b1e8 Merge branch 'feature/login'
|\
| * a1b2c3d Add login page
* | c3f4e2a Update homepage
|/
* 9a8b7c6 Initial project setup

The exact hashes and graph will depend on your repository.

The graph makes the relationships between commits visible. Instead of seeing only a flat list of changes, you can understand how the development lines diverged and came together.

This is one reason merge commits can be valuable in collaborative projects: they preserve the structure of the integration.

6. When does Git create a merge conflict?

So far, Git has been able to combine the changes automatically.

But imagine two developers edit the same line in the same file.

The original login.html contains:

<h1>Welcome</h1>

On main, someone changes it to:

<h1>Welcome back!</h1>

On feature/login, someone changes the same line to:

<h1>Sign in to your account</h1>

Both branches have changed the same part of the file differently.

Git cannot reliably determine which wording the application should use.

It stops and reports a merge conflict.

A merge conflict doesn't mean Git is broken. It means Git needs a human decision about how to combine the changes.

Not every overlapping change causes a conflict, and conflicts aren't limited to identical lines. The exact result depends on the changes and Git's merge rules.

7. Resolve a merge conflict step by step

Let's walk through the process.

First, switch to the branch that should receive the feature:

git switch main

Then start the merge:

git merge feature/login

If Git encounters a conflict, it will report the affected files.

Check the status:

git status

You may see login.html listed as having unmerged paths.

Open the file. Git may have inserted conflict markers that look like this:

<<<<<<< HEAD
<h1>Welcome back!</h1>
=======
<h1>Sign in to your account</h1>
>>>>>>> feature/login

Here's how to read them:

<<<<<<< HEAD marks the start of the current branch's version.

======= separates the two versions.

>>>>>>> feature/login marks the end of the incoming branch's version.

In this example, HEAD refers to the current branch, main. The incoming side comes from feature/login.

These markers are temporary instructions for resolving the conflict. They are not valid final content for the file.

Decide on the final result

You might choose the incoming version:

<h1>Sign in to your account</h1>

Or you might combine the ideas into a new version:

<h1>Welcome back! Sign in to continue.</h1>

The correct choice depends on the application and the intended behavior. Git cannot make that product decision for you.

Remove the conflict markers, save the file, and stage the resolved result:

git add login.html

Check the status again:

git status

When all conflicts have been resolved and staged, complete the merge:

git commit

Git normally prepares a merge commit message for you.

Review it, save it, and close the configured editor. If your Git configuration uses an editor you're unfamiliar with, check its instructions before starting the merge.

The merge is now complete.

What if you want to cancel the merge?

If you're in the middle of resolving a merge and decide you want to return to the pre-merge state, you can usually run:

git merge --abort

Use this while a merge is in progress. If you had uncommitted changes before starting the merge, aborting may not be able to reconstruct every aspect of your original working state in all circumstances. That's why it's best to commit or safely preserve important changes before beginning a merge.

8. A safe merge workflow

Here's a practical sequence you can use when integrating a feature branch:

# Check your current state
git status

# Switch to the branch receiving the changes
git switch main

# Merge the feature branch
git merge feature/login

# Inspect the result
git status
git log --oneline --graph --decorate -10

If conflicts occur, resolve them in the affected files, stage the resolved versions, and complete the merge.

Afterward, run your tests and inspect the final changes before pushing the result to a remote repository.

For team projects, make sure you're working from the intended version of main and following your team's branch and review policies.

A merge can succeed technically while the application still contains a logical bug. Git combines changes; it doesn't replace testing.

9. Can you delete the feature branch afterward?

Yes. Once a feature has been integrated and you no longer need the local branch, you can remove it:

git branch -d feature/login

Git generally refuses to delete a branch with unmerged work, helping protect against accidentally discarding unfinished development.

Deleting the branch removes its reference, not the commits that are already reachable from main.

For a shared repository, deleting the local branch also doesn't automatically delete the remote branch. Remote branch cleanup is a separate operation.

10. Common misconceptions about Git merge

"A merge always creates a merge commit."

Not necessarily. If Git can fast-forward the target branch, it may simply move the branch pointer. A merge commit is typically created when divergent histories need to be joined, unless you choose a different merge strategy or option.

"A conflict means I made a mistake."

No. Conflicts are a normal part of collaborative development. They mean Git needs help determining the intended final content.

"Git automatically picks the correct version."

Git can combine many changes automatically, but it cannot always determine the intended behavior when changes conflict.

"Resolving a conflict means choosing one entire file."

Not at all. You can keep one version, the other version, or combine them into a new result. The important thing is to remove the conflict markers and stage the resolved content.

"Deleting a branch deletes all its commits."

Deleting a branch removes the reference. Commits that remain reachable from other branches, such as main, remain part of the repository history.

The takeaway

Git merge brings independent lines of development together.

Remember these four ideas:

A fast-forward merge moves a branch pointer forward when the existing history allows it.

A three-way merge combines changes from two diverged histories using their common ancestor.

A merge commit records the result and connects the histories through its parent commits.

A merge conflict requires you to decide how the final content should look.

Branches give you room to work independently. Merging lets you bring that work together.

And now you know what happens between those two steps.

What's next?

So far, everything has happened inside a local repository.

But most development involves collaborating with other people and sharing code with a remote repository.

In the next article, we'll explore git fetch, git pull, and git push—three commands that sound similar but do very different jobs.

Next: Local vs Remote — Fetch, Pull, and Push Explained.