# Git Basics You Actually Need to Know - Part 2

In Part 1, we covered commits, branches, and basic Git workflows. You know how to create branches and make commits. Now comes the hard part: bringing those branches back together.

This is where most people get stuck. Merge conflicts, rebasing gone wrong, accidentally deleted commits. Let's demystify all of it.

**This is Part 2 of the Git Basics series.** [Read Part 1 here](https://blog.thecodingant.in/git-basics-you-actually-need-to-know-part-1) for fundamentals like commits, branches, and reset.

## Merging: Bringing Branches Together

You've been working on a feature branch. It's done. Now you need to integrate it back into `main`. That's merging.

```bash
# Switch to the branch you want to merge INTO
git checkout main

# Merge the feature branch
git merge feature/login
```

Git looks at three commits:

1.  The common ancestor (where the branches split)
    
2.  The latest commit on `main`
    
3.  The latest commit on `feature/login`
    

Then it creates a new commit that combines both branches.

![](https://cdn.hashnode.com/uploads/covers/69d007f5e466e2b7625cd1df/757414a6-cc5a-45cc-9a62-64f1209fedb6.svg align="center")

### Fast-Forward Merge

If `main` hasn't changed since you created the branch, Git does a "fast-forward" merge:

```bash
# Before merge
main:    A - B - C
               \
feature:        D - E

# After fast-forward merge
main:    A - B - C - D - E
```

No merge commit needed. Git just moves the `main` pointer forward. Clean and simple.

```bash
git merge feature/login
# Output: Fast-forward
```

### Three-Way Merge

If both branches have new commits, Git creates a merge commit:

```bash
# Before merge
main:    A - B - C - F
               \
feature:        D - E

# After three-way merge
main:    A - B - C - F - M
               \       /
feature:        D - E
```

The merge commit `M` has two parents: `F` from `main` and `E` from `feature`.

```bash
git merge feature/login
# Output: Merge made by the 'recursive' strategy
```

This creates a visible merge in the history. Some teams like this because it shows where features were integrated. Others find it noisy.

## Merge Conflicts: When Git Can't Decide

Conflicts happen when both branches modified the same lines of the same file. Git doesn't know which version to keep.

```bash
git merge feature/login
# Output: CONFLICT (content): Merge conflict in src/app.js
# Automatic merge failed; fix conflicts and then commit the result.
```

Open the conflicted file. You'll see markers:

```javascript
function authenticate(user) {
<<<<<<< HEAD
  return jwt.sign({ id: user.id }, SECRET);
=======
  return jwt.sign({ userId: user.id, role: user.role }, SECRET);
>>>>>>> feature/login
}
```

The section between `<<<<<<< HEAD` and `=======` is your current branch. The section between `=======` and `>>>>>>> feature/login` is the incoming branch.

**Fix it manually:**

```javascript
function authenticate(user) {
  return jwt.sign({ userId: user.id, role: user.role }, SECRET);
}
```

Remove the conflict markers, keep what you want, then:

```bash
git add src/app.js
git commit -m "Merge feature/login"
```

**Abort a merge:**

If you want to give up and start over:

```bash
git merge --abort
```

This puts everything back to before you started the merge.

![](https://cdn.hashnode.com/uploads/covers/69d007f5e466e2b7625cd1df/fa14bc3c-0249-4435-a0dd-963ccdd44881.svg align="center")

## Rebasing: The Alternative to Merging

Rebasing rewrites history. Instead of creating a merge commit, it replays your commits on top of another branch.

```bash
# You're on feature/login
git rebase main
```

What happens:

1.  Git finds the common ancestor
    
2.  Saves your commits (D, E) temporarily
    
3.  Resets your branch to match `main`
    
4.  Replays your commits one by one on top of `main`
    

```bash
# Before rebase
main:    A - B - C - F
               \
feature:        D - E

# After rebase
main:    A - B - C - F
                     \
feature:              D' - E'
```

Notice `D'` and `E'` — these are new commits with different SHAs. Same changes, different history.

![](https://cdn.hashnode.com/uploads/covers/69d007f5e466e2b7625cd1df/773b4c2a-7ca6-482d-9213-709f7dab874e.svg align="center")

### Why Rebase?

**Linear history:** No merge commits cluttering the log. Clean, straight line.

**Easier to read:** `git log --oneline` shows a simple sequence of changes.

**Better for code review:** Each commit stands alone, no merge noise.

### The Golden Rule of Rebasing

**Never rebase commits that you've pushed to a shared branch.**

If other people have based work on your commits, rebasing rewrites history under them. Their branches break. People get angry. Projects get messy.

**Rebase private branches only.** Once you push to a shared branch, merge instead.

### Interactive Rebase

Want to clean up your commits before merging? Interactive rebase lets you rewrite history:

```bash
git rebase -i HEAD~3
```

This opens an editor showing your last 3 commits:

```plaintext
pick a3f2d91 WIP
pick b8e4a12 Fix typo
pick c9d5b23 Add authentication

# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like squash, but discard commit message
# d, drop = remove commit
```

Common operations:

**Squash commits together:**

```plaintext
pick a3f2d91 Add authentication
squash b8e4a12 Fix typo
squash c9d5b23 Add tests
```

Combines all three into one commit.

**Reword a commit message:**

```plaintext
pick a3f2d91 WIP
reword b8e4a12 Add user authentication
```

Changes the commit message without changing the code.

**Drop a commit:**

```plaintext
pick a3f2d91 Add authentication
drop b8e4a12 Debug logging
pick c9d5b23 Add tests
```

Removes that commit entirely.

## Merge vs Rebase: When to Use Which

**Use merge when:**

*   Working on a shared branch
    
*   You want to preserve the full history of when features were integrated
    
*   You're merging a long-lived feature branch into main
    

**Use rebase when:**

*   Updating your feature branch with latest main changes
    
*   Cleaning up local commits before pushing
    
*   You want a linear history
    

**Common workflow:**

```bash
# While working on feature branch, keep it updated
git checkout feature/login
git pull origin main --rebase

# When ready to merge, use merge for the final integration
git checkout main
git merge feature/login
```

This keeps your feature branch clean (rebased) but preserves merge history in main.

## Reflog: The Git Time Machine

You messed up. You ran `git reset --hard` and deleted commits. Or rebased wrong and lost work. Don't panic.

`git reflog` records every time HEAD moves. Every commit, reset, rebase, checkout — it's all there.

```bash
git reflog
```

Output:

```plaintext
c9d5b23 HEAD@{0}: reset: moving to HEAD~2
e2b7d45 HEAD@{1}: commit: Add tests
d1a6c34 HEAD@{2}: commit: Fix authentication
b8e4a12 HEAD@{3}: commit: Add user model
a3f2d91 HEAD@{4}: commit (initial): Initial commit
```

Each entry has:

*   The commit SHA
    
*   What action was taken
    
*   The commit message
    

**Recover a deleted commit:**

You accidentally deleted commits `d1a6c34` and `e2b7d45`. They're in reflog:

```bash
# Create a branch at the lost commit
git branch recovery e2b7d45

# Or reset to it
git reset --hard e2b7d45
```

Your "deleted" commits are back.

![](https://cdn.hashnode.com/uploads/covers/69d007f5e466e2b7625cd1df/936a2c45-3fd7-4dc9-a87a-6f1a14fa6752.svg align="center")

**Reflog expires after 90 days** by default. After that, commits are truly gone. But 90 days is usually enough to realize you made a mistake.

### Recovering from a Bad Rebase

```bash
# You rebased and everything went wrong
git reflog

# Find the commit before the rebase
# HEAD@{1}: rebase: checkout main
# HEAD@{2}: commit: Your last good commit

# Reset to before the rebase
git reset --hard HEAD@{2}
```

The bad rebase is undone. You're back to before you started.

### Recovering Uncommitted Work

Reflog only tracks committed work. If you lost uncommitted changes with `git reset --hard`, they're gone forever.

**Exception:** If you used `git stash`, stashed changes are tracked:

```bash
git reflog show stash
```

But we'll cover stashing in Part 3.

## Cherry-Pick: Copying Commits

Sometimes you want just one commit from another branch, not the whole branch.

```bash
# You're on main, want commit abc123 from feature branch
git cherry-pick abc123
```

This creates a new commit on `main` with the same changes as `abc123`. The original commit stays on the feature branch.

**Use case:** A bug fix was committed to the wrong branch:

```bash
# Bug fix is on feature/login but needed on main
git checkout main
git cherry-pick bug-fix-commit
git checkout feature/login
git rebase main  # Now feature has the fix too
```

**Cherry-pick a range:**

```bash
git cherry-pick abc123..def456
```

Applies all commits from `abc123` to `def456`.

## Handling Complex Merges

### Merge Strategies

Git has different merge strategies for different situations:

**Recursive (default):** Works for most cases. Handles renames well.

**Ours:** In conflicts, always keep our version:

```bash
git merge -X ours feature/login
```

**Theirs:** In conflicts, always keep their version:

```bash
git merge -X theirs feature/login
```

⚠️ These still create a merge commit. They just resolve conflicts automatically.

### Viewing Merge History

See where branches were merged:

```bash
git log --graph --oneline --all
```

Output:

```plaintext
* c9d5b23 (HEAD -> main) Merge feature/login
|\
| * e2b7d45 (feature/login) Add tests
| * d1a6c34 Fix authentication
* | b8e4a12 Update dependencies
|/
* a3f2d91 Initial commit
```

The graph shows exactly how branches diverged and merged.

## Real-World Scenarios

### Scenario 1: Update Feature Branch with Latest Main

```bash
# Option 1: Rebase (cleaner history)
git checkout feature/login
git pull origin main --rebase

# Option 2: Merge (preserves history)
git checkout feature/login
git merge main
```

### Scenario 2: Squash Feature Branch Before Merging

```bash
git checkout feature/login
git rebase -i main

# In the editor, squash all commits into one
# Then merge into main
git checkout main
git merge feature/login
```

### Scenario 3: Undo a Merge

**If you haven't pushed:**

```bash
git reset --hard HEAD~1
```

**If you have pushed:**

```bash
# Create a new commit that undoes the merge
git revert -m 1 HEAD
```

The `-m 1` flag tells Git which parent to revert to (the first parent, which is main).

### Scenario 4: Recover Deleted Branch

```bash
# Find the last commit on the deleted branch
git reflog

# Create a new branch at that commit
git branch recovered-branch abc123
```

## What We Covered

You now understand:

*   Merging: fast-forward vs three-way merges
    
*   Handling merge conflicts step by step
    
*   Rebasing: rewriting history for cleaner logs
    
*   When to merge vs when to rebase
    
*   Interactive rebase for cleaning up commits
    
*   Reflog: recovering "deleted" commits
    
*   Cherry-picking specific commits
    
*   Real-world merge and rebase workflows
    

In Part 3, we'll cover worktrees for working on multiple branches simultaneously, and stashing for saving uncommitted work.

## Common Mistakes to Avoid

**Rebasing pushed commits:** Rewrites history, breaks other people's branches. Merge instead.

**Not pulling before rebasing:** You'll rebase onto old code. Always `git pull` first.

**Force pushing to shared branches:** `git push -f` overwrites remote history. Only force push to your own feature branches.

**Ignoring conflicts:** The conflict markers must be removed. Git won't let you commit with `<<<<<<<` in your files.

**Rebasing main onto feature:** Rebase feature onto main, not the other way around.

* * *

## TLDR;

Merging combines branches by creating a merge commit. Fast-forward merges happen when one branch is ahead. Three-way merges happen when both branches have new commits. Conflicts occur when both branches modify the same lines — fix them manually, remove markers, then commit. Rebasing replays your commits on top of another branch for linear history. Never rebase pushed commits. Use `git rebase -i` to squash, reword, or drop commits. Reflog tracks every HEAD movement for 90 days — use it to recover "deleted" commits with `git reset --hard commit-sha`. Cherry-pick copies specific commits between branches. Merge for final integration, rebase for keeping feature branches clean. Part 3 covers worktrees and stashing.
