Git Branching and Rebasing
Master merge conflicts, rebasing, and interactive rebase in Git.
Introduction
30 Second Summary
When multiple people edit the same file at the same time, someone's changes are going to clash. Git gives you tools to handle those clashes gracefully, keep your history tidy, and present polished work to your team.
In this project, you will create and resolve merge conflicts, rebase branches for clean linear history, and use interactive rebase to polish messy commits into professional entries.
What You'll Build
A professional-quality commit history on your git-learning-log repository that demonstrates three intermediate Git skills: conflict resolution, rebasing for linear history, and interactive rebase for commit cleanup.
By the end of this project, you'll have:
- A resolved merge conflict where you manually chose how to combine competing changes to the same file.
- A feature branch rebased onto main, giving you a straight-line history with no merge commits.
- A cleaned-up commit log where multiple sloppy commits are squashed into one polished entry using interactive rebase.
- Secret Mission: Use git stash to save and restore work-in-progress changes mid-workflow.
Want a complete demo of how to do this project, from start to finish? Check out our 🎬 walkthrough with Maya.
Do I need to complete Parts 1 and 2 first?
This project assumes you are comfortable with branching, committing, pushing, and Pull Requests. If you have not completed Parts 1 and 2 of the Learn Git Fundamentals series, start there first.
Set Up Your Repository
In this project, you'll practice three intermediate Git skills: resolving merge conflicts, rebasing feature branches, and cleaning up commits with interactive rebase.
This project is fully standalone. Whether you completed Parts 1 and 2 or are jumping in fresh, this step gets your tools and repository ready from scratch.
In this step, get ready to:
- Make sure Cursor, Git, and a GitHub account are ready.
- Open the git-learning-log repository in Cursor.
- Pull the latest changes and confirm a clean working tree.
- Create and push a starter file for conflict practice.
Make sure your tools are installed
This project uses three tools: Cursor (a code editor), Git (version control), and a GitHub account (remote repository hosting).
✔️ I already have Cursor, Git, and a GitHub account
Great. Skip ahead to the next substep where you open the git-learning-log repository.
ⓧ I need to set these up
Install Cursor
- Go to cursor.com/download in your browser.
- Click the download button for your operating system.
- Open the downloaded file.
- On macOS, drag Cursor into your Applications folder. On Windows, run the installer and follow the prompts.
- When Cursor opens for the first time, it will ask you to sign in. Create a free account at cursor.com if you do not have one.
Install Git
macOS
- Open the Terminal app (press Cmd+Space, type Terminal, press Enter).
- Check if Git is already installed by running:
git --version
If Git is installed, you will see a version number like git version 2.39.5. If it is not installed, macOS will show a dialog asking you to install Command Line Tools.
- Click Install in the dialog that appears. This downloads Git as part of the Xcode Command Line Tools (about 5-15 minutes).
- Once the install finishes, confirm Git is ready by running git --version again.
Windows
- Open Command Prompt (press the Windows key, type cmd, press Enter).
- Check if Git is already installed by running:
git --version
If you see a version number, Git is installed. If you see git is not recognized, you need to install it.
- Go to git-scm.com/download/win and download the installer.
- Run the downloaded .exe file and follow the prompts. Accept the default options for each screen.
- After installation, close and reopen Command Prompt, then run git --version to confirm.
Linux
- Open your terminal and run:
git --version
If Git is not installed, install it with your package manager:
sudo apt install git-all
- Run git --version again to confirm.
Configure Git with your name and email
Git labels every commit with your identity. Set this up once so your commits are properly attributed.
- Open the integrated terminal in Cursor by pressing Cmd+J (macOS) or Ctrl+J (Windows).
- Set your Git username and email by running these commands (replace with your own name and the email you use for GitHub):
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
- Set the default editor to nano so interactive rebase screens are easier to use:
git config --global core.editor "nano"
Why nano?
Later in this project, Git will open an editor for interactive rebase. The default editor on most systems is vim, which has a steep learning curve. Nano is simpler: you edit text directly and press Ctrl+O to save, Ctrl+X to exit.
Create a GitHub account
- Go to github.com and click Sign up.
- Follow the prompts to create a free account. Use the same email address you just configured in Git.
- Verify your email address when GitHub sends you a confirmation email.
If you already have a GitHub account, skip this. Your tools are ready.
Open the git-learning-log repository in Cursor
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor and press Enter to open it.
Now you need to get the git-learning-log repository open in Cursor. Pick the tab that matches your situation.
✔️ I have the git-learning-log folder on my Desktop
- In Cursor, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop and select the git-learning-log folder, then click Open.
ⓧ I have the repo on GitHub but not locally
If you completed Parts 1 or 2 but no longer have the local folder, clone it from GitHub.
- Open the integrated terminal in Cursor by pressing Cmd+J (macOS) or Ctrl+J (Windows).
- Move to your Desktop by running this command:
cd ~/Desktop
- Clone your repository from GitHub by running this command:
git clone https://github.com/[[GITHUB_USERNAME="your-github-username"]]/git-learning-log.git
- Once the clone finishes, open the folder in Cursor by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop and select the git-learning-log folder, then click Open.
ⓧ I don't have this repo at all
If you are starting fresh and never created a git-learning-log repository, you will create one now.
- Go to github.com and sign in.
- Click the + icon in the top right corner, then click New repository.
- Leave the Owner dropdown set to your personal GitHub account.
- Enter git-learning-log as the repository name.
- Select Public.
- Check the box for Add a README file.
Why add a README file?
The README itself isn't important. Checking that box makes GitHub create an initial commit on the main branch. Without it, the repository would be completely empty with no branches, and the git clone and git checkout main commands later would fail.
- Click Create repository
Now clone it to your computer.
- Open the integrated terminal in Cursor by pressing Cmd+J (macOS) or Ctrl+J (Windows).
- Move to your Desktop by running this command:
cd ~/Desktop
- Clone your new repository by running this command:
git clone https://github.com/[[GITHUB_USERNAME="your-github-username"]]/git-learning-log.git
- Open the folder in Cursor by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop and select the git-learning-log folder, then click Open.
What about authentication?
The first time you push or clone over HTTPS, Git may ask for your GitHub credentials. If a browser window opens asking you to authorize Git, click Authorize and follow the prompts.
If you see an authentication error instead, GitHub now requires a personal access token instead of a password. Go to github.com/settings/tokens, generate a new token with repo scope, and use it as your password when prompted.
Pull the latest changes and confirm a clean state
Before making any changes, you need to make sure your local copy is up to date with what's on GitHub.
- Open the integrated terminal in Cursor by pressing Cmd+J (macOS) or Ctrl+J (Windows).
- Switch to the main branch and pull the latest changes by running these commands:
git checkout main
git pull
What do these commands do?
`git checkout main` switches your working directory to the main branch.
`git pull` fetches new commits from GitHub and merges them into your local branch. This makes sure you're starting from the same point as the remote repository.
- Confirm your working tree is clean by running:
git status
You should see a message that says nothing to commit, working tree clean.
Seeing uncommitted changes or a different branch?
If git status shows modified files, you may have leftover work from a previous session. Run git stash to temporarily set those changes aside, then try git checkout main again.
If you see an error about the branch not existing, run git branch to see which branches are available locally.
help me fix my git status so I have a clean working tree on main
Create the starter file for conflict practice
You'll create a file that two branches will edit in the next step. Starting with a simple file on main gives both branches a shared starting point to conflict on.
- In Cursor's Explorer sidebar on the left, hover over the project name and click the New File icon.
- Name the file conflict-practice.md and press Enter.
- Paste the following content into the new file:
# Conflict Practice
This file is for practicing merge conflict resolution.
Why start with such a simple file?
Merge conflicts happen when two branches change the same lines. By starting with a short, predictable file, you'll be able to clearly see the conflict markers when they appear in Step 2.
- Save the file by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Stage, commit, and push your new file by running these commands in the terminal:
git add conflict-practice.md
git commit -m "Add conflict-practice.md"
git push
What do these commands do?
`git add conflict-practice.md` stages the new file, telling Git to include it in the next commit.
`git commit -m "Add conflict-practice.md"` saves a snapshot of the staged changes with a descriptive message.
`git push` uploads the commit to your GitHub repository so it exists on the remote main branch.
You should see output confirming the push to origin/main.
Authentication error?
If you see an authentication error, your GitHub credentials may have expired. Re-authenticate by following the prompts or regenerating your personal access token on GitHub.
help me fix my git push so I can push to GitHub
✔️ Awesome, I've got everything!
Great. Double check you've saved conflict-practice.md and that your push completed without errors.
ⓧ I'd like to double check the full code
Here is the complete conflict-practice.md file at this point:
# Conflict Practice
This file is for practicing merge conflict resolution.
Your repository is ready with a clean main branch and a starter file on GitHub. Next up, you'll create competing branches that edit the same file to trigger your first merge conflict.
Create and Resolve a Merge Conflict
Merge conflicts happen when two people (or two branches) change the same lines in the same file. Git can't decide which version to keep, so it asks you to resolve the conflict manually.
In this step, you'll deliberately create a conflict so you can practice resolving one in a safe environment. You'll create two branches that edit the same line in conflict-practice.md, then merge them both into main to trigger the conflict.
In this step, get ready to:
- Create two branches that both edit the same line, guaranteeing a conflict.
- Trigger a merge conflict and read the conflict markers.
- Resolve the conflict by combining both changes, then push to GitHub.
Create two branches with competing changes
To guarantee a conflict, both branches need to start from the same point on main and edit the same lines differently. You'll create branch-a first, then return to main to create branch-b.
- Create and switch to branch-a by running this command:
git checkout -b branch-a
- Open conflict-practice.md in Cursor.
- Replace the entire file contents with the following:
# Conflict Practice
## My Workflow Preferences
- I prefer rebasing feature branches for a clean linear history
What does this change do?
You're replacing the placeholder text with a "My Workflow Preferences" section. This is the content that will conflict with branch-b's changes later.
- Save conflict-practice.md.
- Stage and commit your changes on branch-a by running these commands:
git add conflict-practice.md
git commit -m "Add workflow preferences section"
You should see a message confirming the commit was created on branch-a.
Now switch back to main and create branch-b. This ensures branch-b starts from the original version of the file, not from branch-a's changes.
- Switch to main and create branch-b by running these commands:
git checkout main
git checkout -b branch-b
- Open conflict-practice.md in Cursor again. Notice the file is back to its original content because branch-b was created from main.
- Replace the entire file contents with the following:
# Conflict Practice
## Tools I Use
- Cursor for editing
- Git CLI for version control
- GitHub for collaboration
Why different content on each branch?
Branch-a replaced the placeholder with a "Workflow Preferences" section. Branch-b replaced the same placeholder with a "Tools I Use" section. Both branches modified the same lines differently. Git won't know which version to keep.
- Save conflict-practice.md.
- Stage, commit, and switch back to main by running these commands:
git add conflict-practice.md
git commit -m "Add tools section"
git checkout main
You're now back on main with two branches ready to collide.
Seeing 'error: Your local changes would be overwritten'?
This means you have unsaved or uncommitted changes. Make sure you saved the file and ran both git add and git commit before switching branches.
Help me fix this checkout error.
Merge branch-a and trigger the conflict
First you'll merge branch-a into main, which succeeds cleanly. Then you'll try to merge branch-b, which triggers the conflict because both branches changed the same lines.
- Merge branch-a into main by running this command:
git merge branch-a
This merge succeeds because main hasn't changed since branch-a was created. Git simply fast-forwards main to include branch-a's commit.
- Now attempt to merge branch-b by running this command:
git merge branch-b
This time the merge fails. You should see a message mentioning CONFLICT (content) and Automatic merge failed; fix conflicts and then commit the result.
What just happened?
Git tried to combine branch-b's changes with main (which now includes branch-a's changes). Both branches modified the same lines in conflict-practice.md, so Git can't automatically decide which version to keep.
Git paused the merge and placed conflict markers in the file so you can manually choose what stays.
- Open conflict-practice.md in Cursor to see the conflict markers.
You should see something like this:
# Conflict Practice
<<<<<<< HEAD
## My Workflow Preferences
- I prefer rebasing feature branches for a clean linear history
=======
## Tools I Use
- Cursor for editing
- Git CLI for version control
- GitHub for collaboration
>>>>>>> branch-b
What do the conflict markers mean?
- <<<<<<< HEAD marks the start of the current branch's version (main, which has branch-a's content).
- ======= separates the two conflicting versions.
- >>>>>>> branch-b marks the end of the incoming branch's version.
Your job is to delete all three marker lines and decide what the final file should look like.
Resolve the conflict and commit the result
Instead of picking one side or the other, you'll combine the best of both changes into a single file. This is the most common resolution strategy on real teams.
What about Accept Current Change or Accept Both Changes?
Cursor shows shortcut buttons above the conflict: Accept Current Change keeps only the HEAD version, Accept Incoming Change keeps only branch-b's version, and Accept Both Changes concatenates both sections as-is.
In real projects, conflicts are rarely this clean. You'll often need to rewrite lines, remove duplicates, or restructure code so the two contributions actually work together. Clicking a button skips the review step entirely. Resolving manually teaches you to read the conflict, understand both contributions, and make a deliberate decision about the final result.
- Delete the three marker lines (<<<<<<< HEAD, =======, and >>>>>>> branch-b) and keep all the content from both sides.
- Under 'My Workflow Preferences', add a new bullet: - I resolve conflicts by combining the best of both changes.
- Check your resolved file matches the following:
# Conflict Practice
## My Workflow Preferences
- I prefer rebasing feature branches for a clean linear history
- I resolve conflicts by combining the best of both changes
## Tools I Use
- Cursor for editing
- Git CLI for version control
- GitHub for collaboration
What does this resolution do?
You kept the "Workflow Preferences" section from branch-a, the "Tools I Use" section from branch-b, and added a new bullet acknowledging your resolution strategy. This is a real-world resolution. You chose to keep both contributions.
- Save conflict-practice.md.
- Mark the conflict as resolved and commit by running these commands:
git add conflict-practice.md
git commit -m "Resolve merge conflict: combine workflow and tools sections"
Why git add before committing?
Running git add on a conflicted file tells Git that you've resolved the conflict. Without it, Git still considers the file unresolved and won't let you commit.
- Push the resolved main branch to GitHub by running this command:
git push
git push origin main vs git push
Since you ran git push -u origin main earlier in this project, Git already knows where to push. From now on, you can use just git push and it does the same thing.
The full form (git push origin main) is always safe because it's explicit. The short form (git push) is quicker once your upstream is set. Both are correct.
- Verify your work by running this command:
git log --oneline
Seeing a scrollable view instead of your normal prompt?
This happens when the log output is longer than your terminal window. Git opens a program called a pager to let you scroll through it. You can tell you're in the pager because the bottom of the screen shows a : or the word END.
Press q to exit the pager and return to your normal terminal. If your output is short enough to fit on screen, it prints directly and no q is needed.
You should see your merge resolution commit at the top of the log, followed by the commits from branch-a and branch-b.
Still seeing conflict markers in the file?
Make sure you deleted all three marker lines (<<<<<<<, =======, and >>>>>>>) from the file before saving. If you need to start over, run git merge --abort to cancel the merge and try again.
Help me resolve this conflict.
✔️ Awesome, I've got everything!
Great. Double check that you've saved your file and pushed to GitHub.
ⓧ I'd like to double check the full code
Here's what your conflict-practice.md should look like after resolving the conflict:
# Conflict Practice
## My Workflow Preferences
- I prefer rebasing feature branches for a clean linear history
- I resolve conflicts by combining the best of both changes
## Tools I Use
- Cursor for editing
- Git CLI for version control
- GitHub for collaboration
You just resolved your first merge conflict. That's a skill every professional developer uses regularly. Next up, you'll learn how to use rebase to avoid merge commits altogether and keep your history perfectly linear.
Rebase a Feature Branch onto Main
You just proved you can handle merge conflicts without breaking a sweat. Now you will learn a technique that avoids those messy merge commits entirely.
Rebasing replays your feature branch commits on top of the latest main branch. The result is a clean, straight-line history that reads as if you started your work after your teammate finished theirs.
In this step, get ready to:
- Create a feature branch with new commits, then simulate a teammate pushing to main.
- Rebase your feature branch onto main's latest commit.
- Fast-forward merge the rebased branch and confirm a linear history.
Create a feature branch and simulate divergent history
To see rebasing in action, you need two branches that have diverged. You will create a feature branch with a commit, then switch back to main and add a separate commit to simulate a teammate's work.
- Create and switch to a new feature branch by running this command:
git checkout -b feature/rebase-practice
- Open conflict-practice.md in Cursor.
- Add the following content at the bottom of the file, below the Tools I Use section:
## Lessons Learned
- Merge conflicts are normal and not scary
- Rebase rewrites history, so only use it on local branches
- Interactive rebase is powerful for cleaning up work before sharing
What does this content do?
This adds a new section to your practice file. The content itself previews concepts you will learn in the rest of this project.
- Save conflict-practice.md.
- Stage and commit the change on your feature branch by running these commands:
git add conflict-practice.md
git commit -m "Add lessons learned section"
Your feature branch is now one commit ahead of main. Next, you will give main its own new commit so the two branches diverge.
- Switch back to main by running:
git checkout main
- Create a new file called team-updates.md and commit it to main by running these commands:
echo "# Team Updates" > team-updates.md
git add team-updates.md
git commit -m "Add team updates file"
What does this command do?
echo "# Team Updates" > team-updates.md creates a new file called team-updates.md and writes a Markdown heading into it. The > redirects the text into the file instead of printing it to the terminal.
You just need main to have a commit that your feature branch does not have. This simulates a real scenario where a teammate pushed new work while you were building your feature.
You should see a commit confirmation message. A new team-updates.md file now appears in Cursor's Explorer sidebar.
Now main and your feature branch have each moved forward independently. This is the classic setup where rebasing shines.
Seeing 'not a git repository'?
Make sure your terminal is inside the git-learning-log folder. Run pwd to check your current location.
help me figure out why Git says this is not a repository
Rebase the feature branch onto main
Rebasing takes your feature branch commits, temporarily sets them aside, updates your branch to main's latest state, then replays your commits on top. The result is a history that looks like you started your feature after the teammate's commit.
- Switch to your feature branch by running:
git checkout feature/rebase-practice
- Rebase your feature branch onto main by running:
git rebase main
You should see output indicating your commit was successfully replayed onto main's latest commit.
What just happened?
Git took your "Add lessons learned section" commit, rewound the branch to match main (which now includes the teammate's commit), then re-applied your commit on top.
Your feature branch is now directly ahead of main with no fork in the history. This makes the upcoming merge trivial.
💡 Why rebase instead of merge?
A regular merge would create an extra "merge commit" that clutters the log with messages like "Merge branch 'feature/rebase-practice' into main". Rebasing produces a linear history that is easier to read and review.
The golden rule: only rebase commits that have not been pushed to a shared branch. Once commits are shared, use merge instead.
🙋♀️ Rebase failed with a conflict?
If you see conflict markers, it means both branches edited the same lines. Resolve the conflict the same way you did in Step 2, then run:
git add .
git rebase --continue
If you want to start over, run git rebase --abort to return to the pre-rebase state.
help me resolve this conflict during my rebase
Fast-forward merge and verify linear history
Because your feature branch is now directly ahead of main (thanks to the rebase), Git can perform a fast-forward merge. This simply moves main's pointer forward without creating any merge commit.
- Switch to main by running:
git checkout main
- Merge the rebased feature branch into main by running:
git merge feature/rebase-practice
You should see the words "Fast-forward" in the output, confirming no merge commit was created.
- Check your commit history by running:
git log --oneline
What should I see?
Your log shows every commit in a straight line, one after the other. There is no "Merge branch..." entry like you saw in Step 2. That extra merge commit is what makes history look messy and branched. Because you rebased first, Git was able to skip it entirely.
You should see your "Add lessons learned section" commit sitting directly on top of the teammate's "Add team updates file" commit, as if you wrote yours after theirs.
If the output opens in a scrollable pager (you see a : at the bottom), press q to exit back to your terminal.
- Push the updated main branch to GitHub by running:
git push origin main
Push rejected?
If your push is rejected because the remote is ahead, pull first with git pull --rebase origin main and then push again.
help me figure out why my push to main was rejected
✔️ Awesome, I've got everything!
Great. Double check you have saved conflict-practice.md and that your push to GitHub succeeded.
ⓧ I'd like to double check the full code
Here is what your conflict-practice.md should look like at the end of this step:
# Conflict Practice
## My Workflow Preferences
- I prefer rebasing feature branches for a clean linear history
- I resolve conflicts by combining the best of both changes
## Tools I Use
- Cursor for editing
- Git CLI for version control
- GitHub for collaboration
## Lessons Learned
- Merge conflicts are normal and not scary
- Rebase rewrites history, so only use it on local branches
- Interactive rebase is powerful for cleaning up work before sharing
Your commit history is now clean and linear. Next up, you will learn how to polish messy commit messages and combine multiple small commits into one using interactive rebase.
Clean Up Commits with Interactive Rebase
Your rebased branch from the last step produced a beautifully linear history. But what about the commits themselves?
In real projects, you often make messy work-in-progress commits while figuring things out. Messages like "wip" and "fix typo" are fine while you're working, but they clutter the history for anyone reading it later. Interactive rebase lets you rewrite those commits before sharing them. You can rename messages, combine multiple commits into one, or even remove commits entirely.
In this step, get ready to:
- Create a feature branch and deliberately make four messy commits with bad messages.
- Use interactive rebase to reword a commit message and squash commits together.
- Push the polished branch and open a Pull Request showing one clean commit.
Create a feature branch and make four messy commits
You are going to deliberately create a messy commit history, then clean it up. This simulates what happens during real development when you commit frequently while figuring things out.
- Create a new feature branch and switch to it by running this command:
git checkout -b feature/interactive-rebase
Now you will make four quick commits, each with a deliberately sloppy message. These represent the kind of "thinking out loud" commits developers make while working.
- In Cursor, open conflict-practice.md.
- Scroll to the Lessons Learned section at the bottom of the file.
- Add this line below the last bullet (include the deliberate typo):
- Git stash is a lifesavr for context switching
- Save the file, then stage and commit with a sloppy message by running:
git add .
git commit -m "wip"
Seeing 'You have unstaged changes'?
If you ever see this error during a Git operation (like rebase, checkout, or pull), it means Git detected modified files that have not been staged or committed. Git refuses to continue because it does not want to overwrite your work.
This often happens silently in editors like Cursor that auto-save. An accidental keystroke or a formatting change can dirty your working tree between commits without you noticing.
- Option 1: Run git stash to set the changes aside temporarily. Once your command finishes, run git stash pop to bring them back.
- Option 2: Run git add . followed by git commit -m "save progress" to commit the changes before continuing.
Help me fix my unstaged changes error
- Back in conflict-practice.md, fix the typo by changing lifesavr to lifesaver.
- Save the file, then commit the fix by running:
git add .
git commit -m "fix typo"
- Now scroll up to the My Workflow Preferences section. Add this line below the last bullet (include the deliberate typo):
- I use interctive rebase to polish commits before opening a PR
- Save the file, then commit by running:
git add .
git commit -m "more stuff"
- Fix the typo by changing interctive to interactive.
- Save the file, then make the final sloppy commit by running:
git add .
git commit -m "done maybe"
- Verify your four messy commits by running:
git log --oneline -5
You should see your four commits listed with their sloppy messages: done maybe, more stuff, fix typo, and wip.
- Push this messy branch to GitHub so you can see the difference after cleaning it up:
git push -u origin feature/interactive-rebase
Why push the messy branch first?
Pushing now gives you a remote copy of the messy history. After you clean it up locally with interactive rebase, you will force-push the polished version. This simulates the real workflow where you push work-in-progress, then rewrite history before opening a Pull Request.
🙋♀️ Push rejected or authentication error?
Make sure you are on the feature/interactive-rebase branch (run git branch to check). If you see an authentication error, verify your GitHub credentials are configured correctly.
Help me fix my git push error
Rewrite history with interactive rebase
Those four commits all work toward a single goal: adding two lines to your file. A reviewer reading your history does not need to see each typo fix individually. Interactive rebase lets you combine them into one clean commit with a professional message.
- Set your terminal editor to nano (a beginner-friendly editor) by running this command:
git config --global core.editor "nano"
Why set the editor to nano?
Interactive rebase opens a terminal text editor so you can rearrange and label your commits. Without this config, Git defaults to vim, which uses keyboard shortcuts that are not intuitive for beginners (you cannot even type normally without pressing i first).
Nano lets you type and navigate normally, just like a regular text editor. You will use it multiple times in this substep.
- Start the interactive rebase for your last 4 commits by running:
git rebase -i HEAD~4
Your terminal editor opens with a list of your four commits. It looks something like this:
pick a1b2c3d wip
pick e4f5g6h fix typo
pick i7j8k9l more stuff
pick m0n1o2p done maybe
What am I looking at?
Each line is one commit, listed from oldest at the top to newest at the bottom (the opposite of git log). The word at the start of each line is the command Git will apply to that commit.
The two commands you will use are reword (keep the commit but edit its message) and squash (combine the commit with the one above it and edit the combined message).
- Change the first line's pick to reword. This tells Git you want to keep this commit but change its message.
- Change the remaining three lines from pick to squash. This combines each commit into the one above it.
Your editor should now look like this:
reword a1b2c3d wip
squash e4f5g6h fix typo
squash i7j8k9l more stuff
squash m0n1o2p done maybe
- Save and exit the editor (Ctrl+O, Enter, then Ctrl+X in nano).
What do these nano shortcuts mean?
- Ctrl+O = "Write Out." This saves your changes to the file.
- Enter = confirms the filename nano suggests (just accept the default).
- Ctrl+X = exits nano and returns you to the terminal.
You will use this same sequence every time you save and exit in nano throughout this substep.
Git now processes your commands. First, it opens the editor for the reword step. You will see the old message wip ready to be edited.
- Delete the old message and type a professional commit message:
Add git stash lesson and interactive rebase preference
- Save and exit the editor (Ctrl+O, Enter, then Ctrl+X in nano).
Git then processes the three squash commands and opens the editor one more time. You will see all four commit messages combined together. This is where you write the final message for the single squashed commit.
- Delete everything in the editor and type the same clean message:
Add git stash lesson and interactive rebase preference
- Save and exit the editor one final time.
You should see a message confirming the rebase was successful. Your four messy commits are now one polished commit.
- Verify the cleanup by running:
git log --oneline -3
You should see a single commit with your professional message instead of the four sloppy ones.
What just happened?
Interactive rebase rewrote your branch's history. The reword command changed the first commit's message from "wip" to something meaningful. The squash commands folded the other three commits into the first, keeping all the file changes but discarding the messy messages.
The result is one commit that represents a complete unit of work with a clear description. This is exactly what a reviewer wants to see in a Pull Request.
🙋♀️ Rebase went wrong?
If you made a mistake in the editor or see an error, you can abort the entire rebase and try again by running git rebase --abort. This returns your branch to the exact state it was in before you started.
Help me fix my interactive rebase
Push the cleaned-up branch and open a Pull Request
Your local branch now has clean history, but the remote copy on GitHub still has the four messy commits you pushed earlier. You need to overwrite the remote branch with your rewritten history.
- Push your rewritten branch to GitHub by running:
git push origin feature/interactive-rebase --force-with-lease
Why can't I just use git push?
A regular git push gets rejected here. Git sees that your local history no longer matches the remote (because you rewrote it with interactive rebase), so it blocks the push to protect you from accidentally overwriting work.
A "force push" tells Git "I know the histories don't match — overwrite the remote with my local version." This is necessary after any history rewrite like interactive rebase.
The difference between the two flags is safety. --force overwrites the remote blindly, even if a teammate pushed new commits you have not seen. --force-with-lease checks first — if the remote has changed since you last pulled, it refuses to overwrite. Always use --force-with-lease to protect against accidentally wiping someone else's work.
- Open your browser and go to your git-learning-log repository on GitHub.
- You should see a banner saying your branch had recent pushes. Click Compare & pull request.
Don't see the Compare & pull request banner?
The banner sometimes disappears after a few minutes. Click the Pull requests tab at the top of your repository page, then click New pull request. Set the compare branch to feature/interactive-rebase and click Create pull request.
Help me open a Pull Request on GitHub
- Leave the title as the default (your polished commit message) and click Create pull request.
- Scroll down to the Commits section on the Pull Request page.
You should see exactly one commit with your professional message. Four messy commits condensed into a single clean unit of work.
- Click Squash and merge.
- Click Confirm squash and merge.
Wait, didn't I already squash these?
Since your PR already has one commit, "Squash and merge" just merges it as-is. It's the safest merge button to use because if you'd accidentally left multiple commits, GitHub would catch it.
Some teams skip interactive rebase entirely and rely on this button to squash at merge time. The tradeoff is that you lose control over the final message and can't review the combined diff before it lands. Interactive rebase gives you that control.
Your polished commit is now part of main. It's served its purpose as a demonstration. Interactive rebase turned four messy work-in-progress commits into a clean, professional history before merging.
- Now sync your local main with GitHub by running:
git checkout main
git fetch origin
git reset --hard origin/main
Your local main now matches GitHub exactly. You are ready for the Secret Mission.
Why not just git pull?
Throughout this project, you merged Pull Requests on GitHub using "Squash and merge." Each time, GitHub created a brand new commit on remote main that doesn't match the commits on your local main. A regular git pull would try to reconcile these mismatched histories and likely cause merge conflicts.
git fetch origin downloads the latest state from GitHub without changing anything locally. git reset --hard origin/main then says "throw away my local main and make it identical to what GitHub has." This is safe because all your work was already merged through Pull Requests. Nothing is lost.
🙋♀️ Already tried git pull and got stuck?
If you already tried git pull and got stuck with merge conflicts or a "divergent branches" warning, run these commands to get back on track:
git merge --abort
git fetch origin
git reset --hard origin/main
This cancels any in-progress merge and resets your local main to match GitHub. You are back on the right path.
Help me get my local main back in sync
✔️ Awesome, I've got everything!
Great. Double check you have saved conflict-practice.md and that your Pull Request shows one clean commit on GitHub.
ⓧ I'd like to double check the full code
Here is the final state of conflict-practice.md after all your commits:
# Conflict Practice
## My Workflow Preferences
- I prefer rebasing feature branches for a clean linear history
- I resolve conflicts by combining the best of both changes
- I use interactive rebase to polish commits before opening a PR
## Tools I Use
- Cursor for editing
- Git CLI for version control
- GitHub for collaboration
## Lessons Learned
- Merge conflicts are normal and not scary
- Rebase rewrites history, so only use it on local branches
- Interactive rebase is powerful for cleaning up work before sharing
- Git stash is a lifesaver for context switching
You now have three professional Git skills in your toolkit: conflict resolution, rebasing for linear history, and interactive rebase for commit cleanup. Ready for a bonus challenge? The Secret Mission will teach you one more trick that ties it all together.
Secret mission
Save Work-in-Progress with Git Stash
What happens when you're mid-task on a feature branch and a teammate needs an urgent fix on main? You can't commit half-finished work. Learn how to use git stash to save your changes, switch context, and come back without losing anything.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally and uses a free GitHub repository, so there are no ongoing costs. Decide whether to keep your resources available, or delete them entirely.
Resources you used:
- Local git-learning-log folder on your Desktop (no cost).
- GitHub git-learning-log repository with additional commits, branches, and Pull Requests (free).
Keep everything running
No action needed. Choose this if you plan to continue practicing Git or want to reference your commit history later.
- GitHub repositories are free. Your repository will stay available at no cost.
- Your local folder takes minimal disk space.
Pause - I'll come back to this later
There is nothing to pause. There are no running processes or recurring costs associated with this project.
- Your local folder and GitHub repository sit idle without consuming any resources.
Delete - I don't want to use this again
Remove all project resources and start fresh.
- Delete the local folder by running this command in your terminal:
macOS
rm -rf ~/Desktop/git-learning-log
Windows
rmdir /s /q $HOME\Desktop\git-learning-log
- To delete the GitHub repository:
- Go to your git-learning-log repository page on GitHub.
- Click Settings in the top navigation bar.
- Scroll down to the Danger Zone section at the bottom of the page.
- Click Delete this repository and follow the confirmation prompts.
Nice Work!
Nice Work!
You just leveled up from "can use Git" to "can handle the messy parts of Git that trip everyone up." Your repository now proves you can resolve conflicts, keep history clean with rebase, and polish commits before sharing them.
You've learned how to:
- Resolved a merge conflict by reading conflict markers and manually combining competing changes into a single resolution.
- Rebased a feature branch onto main to produce a clean linear history, avoiding unnecessary merge commits.
- Used interactive rebase to reword commit messages and squash multiple commits into one polished entry.
- Secret Mission: Used git stash to save and restore work-in-progress changes while switching branches.
Ready to quiz yourself?