Git Branching, PRs, and Merge Conflicts

Practice real team Git workflows: branches, conflicts, and pull requests.

Introduction

30 Second Summary

Imagine two people editing the same sentence in a shared document at the same time. When they both hit save, someone has to decide which version wins. That is exactly what happens in code when two branches change the same line.

In this project, you will build a practice repository, create branches that deliberately collide, resolve the conflict by hand, and then lock down your main branch so every future change must go through a Pull Request before it can be merged.

What You'll Build

By the end, you will have a GitHub repository where you can open a pull request, review the diff, and squash-merge it into a protected main branch. This is the exact workflow you will use when reviewing AI-generated code on Gavelogy.

By the end of this project, you'll have:

  • A feature branch workflow where you create branches, push them, and see them appear on GitHub.
  • A resolved merge conflict where you read the conflict markers and confidently chose the correct change.
  • A protected main branch with branch protection rules that block direct pushes and force all changes through a reviewed, squash-merged pull request.
  • Secret Mission: A reusable PR template that structures every future code review with consistent sections for what changed, why, and how to test it.

Are there any prerequisites?

You need Git installed on your machine, a free GitHub account, and familiarity with basic Git commands like git add, git commit, and git push. You will also need Windsurf editor (or any code editor) for editing files.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Practice Repository

You need a fresh playground repo on GitHub so you can experiment with branches and conflicts without risking your real Gavelogy codebase.

In this step, you will create a new repository, add a simple Team Config Dashboard page, and push your first commit to main.

In this step, get ready to:
  • Create a new public GitHub repository.
  • Add the Team Config Dashboard HTML file and push your initial commit.
  • Verify the file appears on GitHub.
Create a new repository on GitHub

You will create an empty repository on GitHub. Keeping it empty (no README, no .gitignore) means you can push your local code to it without any merge conflicts right away.

  • Open github.com in your browser and sign in to your account.
  • In the upper-right corner, click the + menu and select New repository.
  • In the Repository name field, type git-workflow-practice.
  • Select Public for the repository visibility.
  • Leave Add a README file unchecked.
  • Leave the Add .gitignore dropdown set to None.
  • Click Create repository.

GitHub shows a Quick Setup page with instructions for pushing code. You will use this page in a moment to grab your clone URL.

Why leave the repository empty?

An empty repository has no commits yet. This lets you push your first local commit cleanly without needing to merge or rebase against a remote README.

Clone the repo and add the dashboard page

Now you will clone the empty repo to your machine, create the HTML file, and push it up.

  • In Windsurf, open the integrated terminal by pressing Ctrl+` (macOS) or Ctrl+` (Windows).
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Clone your empty repository by running this command:
git clone https://github.com/[[GITHUB_USERNAME="your-github-username"]]/git-workflow-practice.git

Where do I find my clone URL?

On the Quick Setup page GitHub showed after creating the repo, copy the HTTPS URL. It follows the pattern https://github.com/USERNAME/git-workflow-practice.git.

You will see a warning that you cloned an empty repository. That is expected.

  • Move into the project folder by running:
cd git-workflow-practice
  • Open this folder in Windsurf by clicking File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop and select the git-workflow-practice folder, then click Open.

Windsurf now shows the empty folder in the Explorer sidebar. The integrated terminal automatically opens in the project root.

  • Create a new file called index.html by clicking the new file icon in the Explorer sidebar.
  • Name it index.html and paste in the following code:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Team Config Dashboard</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; }
        h1 { color: #333; }
        .config-item { background: #f5f5f5; padding: 12px; margin: 8px 0; border-radius: 4px; }
        .config-item span { font-weight: bold; }
    </style>
</head>
<body>
    <h1>Team Config Dashboard</h1>
    <p>Project: Gavelogy Practice</p>
    <div class="config-item">
        <span>Environment:</span> development
    </div>
    <div class="config-item">
        <span>Theme:</span> light
    </div>
    <div class="config-item">
        <span>Notifications:</span> enabled
    </div>
</body>
</html>

What does this file do?

This is a simple config dashboard that displays three team settings: Environment, Theme, and Notifications. Each setting lives in its own styled div.

The Theme line is the one you will fight over in later steps when two branches try to change it to different values.

  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Confirm the file appears in the Explorer sidebar.

Don't see index.html in the sidebar?

Make sure you opened the git-workflow-practice folder (not your Desktop). Check that the file was saved with the .html extension.

Help me figure out why my file isn't showing up

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before continuing.

ⓧ I'd like to double check the full code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Team Config Dashboard</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; }
        h1 { color: #333; }
        .config-item { background: #f5f5f5; padding: 12px; margin: 8px 0; border-radius: 4px; }
        .config-item span { font-weight: bold; }
    </style>
</head>
<body>
    <h1>Team Config Dashboard</h1>
    <p>Project: Gavelogy Practice</p>
    <div class="config-item">
        <span>Environment:</span> development
    </div>
    <div class="config-item">
        <span>Theme:</span> light
    </div>
    <div class="config-item">
        <span>Notifications:</span> enabled
    </div>
</body>
</html>
  • Stage and commit the file by running these commands in the Windsurf terminal:
git add index.html
git commit -m "Initial commit: add config dashboard"
  • Ensure the branch is named main and push to GitHub by running:
git branch -M main
git push -u origin main

What do these commands do?

git add index.html stages the file so Git knows to include it in the next commit.

git commit -m saves a snapshot of your staged changes with a descriptive message.

git branch -M main renames your current branch to main (in case your Git defaults to a different name).

git push -u origin main uploads your commit to GitHub and sets origin main as the default push target.

Before you check the result, what do you expect git log --oneline to show?

  • Verify your commit by running:
git log --oneline

You should see one line with a short commit hash followed by Initial commit: add config dashboard.

Push was rejected or asked for credentials?

If Git asks for a username and password, GitHub no longer accepts password authentication for HTTPS. You need a Personal Access Token or SSH key configured. Visit your GitHub Settings > Developer settings > Personal access tokens to generate one.

If you see an error about the remote being empty or the branch not existing, confirm you ran git branch -M main before pushing.

Help me troubleshoot my push to GitHub

Confirm your file on GitHub
  • Open your repository page in the browser at github.com/your-github-username/git-workflow-practice.
  • Confirm that index.html appears in the file list on the main branch.

You should see the file name and your commit message listed on the repository page. Your practice repo is live and ready.

Your playground repository is ready with a clean first commit. Next up, you will create competing feature branches that change the same line of code.

Create Competing Feature Branches

Your repo is live on GitHub with an initial commit. Now you need to simulate what happens on a real team: two developers (or an AI agent and a human) each create their own branch and edit the same file.

On a real team, multiple branches often change the same file at the same time. You need to see how branches diverge before you can understand why conflicts happen. In this step, you will create two branches that each change the Theme line in index.html to a different value.

In this step, get ready to:
  • Create a feature/dark-theme branch and change the Theme to "dark".
  • Create a feature/minimal-theme branch and change the Theme to "minimal".
  • Confirm all three branches appear on GitHub.
Create the dark-theme branch

A feature branch is a copy of your code where you can make changes without affecting main. You will create one now for a dark theme change.

  • In your terminal, create and switch to a new branch by running this command:
git checkout -b feature/dark-theme

You should see: Switched to a new branch 'feature/dark-theme'.

  • In Windsurf, open index.html and find this line:
        <span>Theme:</span> light
  • Change light to dark, so the line now reads:
        <span>Theme:</span> dark
  • Save index.html.
  • Stage, commit, and push your changes by running these commands:
git add index.html
git commit -m "feat: switch theme to dark mode"
git push origin feature/dark-theme

What do these commands do?

  • git add index.html stages the file so Git knows to include it in the next commit.
  • git commit -m "..." saves a snapshot of your staged changes with a descriptive message.
  • git push origin feature/dark-theme uploads this branch to GitHub so it exists remotely too.

Your terminal should confirm the push succeeded with output that includes the branch name feature/dark-theme.

Push rejected or authentication error?

  • Make sure you created the repo as public on GitHub. Private repos on free plans may have different push behaviour.
  • If you see a credentials prompt, follow the instructions to authenticate with GitHub (HTTPS token or SSH key).

Help me fix a git push authentication error

Create the minimal-theme branch

Now you will create a second branch that changes the same Theme line to a different value. The key detail: you must branch from main again, not from the dark-theme branch. This means both branches start from the same point but make conflicting edits.

  • Switch back to main by running:
git checkout main

You should see: Switched to branch 'main'. If you open index.html now, the Theme line is back to light. Your dark-theme edit only exists on that other branch.

  • Create and switch to the second feature branch by running:
git checkout -b feature/minimal-theme
  • In Windsurf, confirm index.html still shows the Theme line as:
        <span>Theme:</span> light
  • Change light to minimal, so the line now reads:
        <span>Theme:</span> minimal
  • Save index.html.
  • Stage, commit, and push this branch by running:
git add index.html
git commit -m "feat: switch theme to minimal"
git push origin feature/minimal-theme

Your terminal should confirm the push with the branch name feature/minimal-theme.

Seeing your dark-theme edit instead of light?

You likely branched from feature/dark-theme instead of main. Run git checkout main first, delete the incorrect branch with git branch -D feature/minimal-theme, then create it fresh from main.

Help me fix my branch that started from the wrong place

Verify both branches on GitHub

Before you check GitHub, think about what you expect to see. You created two branches and pushed both. How many branches should the dropdown show?

  • Open your git-workflow-practice repo on GitHub in your browser.
  • Click the branch dropdown (it shows main by default near the top left of the file list).

You should see three branches listed: main, feature/dark-theme, and feature/minimal-theme.

Why does this matter?

Both feature branches started from the same main commit but changed the same line differently. Git has no way to know which edit is "correct" when you try to combine them. That collision is exactly what causes a merge conflict.

In the next step, you will merge both branches into main and see what happens when Git encounters this collision.

Only seeing one or two branches?

  • Make sure you ran git push origin <branch-name> for each branch. Without the push, the branch only exists locally.
  • Refresh the GitHub page. Sometimes the dropdown needs a page reload to show newly pushed branches.

Help me figure out why my branches are not showing up on GitHub

You now have two branches that both edit the same line. Next up, you will merge them into main and hit your first real merge conflict.

Hit a Merge Conflict and Resolve It

In the previous step, you created two feature branches that both changed the same Theme line in index.html.

When two branches edit the same line, Git cannot pick a winner automatically. This is the exact situation you will face when reviewing AI-generated PRs that touch the same code you edited. You need to resolve this by hand at least once to understand what is happening.

In this step, get ready to:
  • Merge the dark-theme branch cleanly into main.
  • Hit a merge conflict when merging the minimal-theme branch.
  • Resolve the conflict by hand and push the result to GitHub.
Merge the dark-theme branch into main

Right now you are on the feature/minimal-theme branch. You need to switch back to main before you can merge anything into it.

  • Switch to main by running this command:
git checkout main
  • Merge feature/dark-theme into main by running:
git merge feature/dark-theme

This merge succeeds cleanly because main has not changed the Theme line since both branches started. Git simply brings in the "dark" change with no problems.

  • Push the updated main to GitHub by running:
git push origin main

Why did this merge work?

When you created feature/dark-theme, the Theme line on main still said "light". No one else changed that line on main in the meantime, so Git can simply replace "light" with "dark". No conflict.

The Theme line on main now reads "dark". Keep that in mind for the next merge.

Attempt to merge the minimal-theme branch

Now main says Theme: dark. The feature/minimal-theme branch says Theme: minimal. Both branches changed the same line from the same starting point ("light"), but they changed it to different values.

Before you run the next merge, what do you think will happen? Will Git be able to choose between "dark" and "minimal" on its own?

  • Try merging feature/minimal-theme by running:
git merge feature/minimal-theme

Git prints a conflict message and stops the merge:

Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

What just happened?

Git found that both main and feature/minimal-theme changed the same line. It cannot pick a winner, so it pauses and asks you to decide. The merge is not complete yet.

  • Check the state of the conflict by running:
git status

You should see index.html listed as "both modified". That means both branches touched this file and Git needs your help.

  • Open index.html in Windsurf to see the conflict markers.

Inside the file, you will find a section that looks like this:

<<<<<<< HEAD
        <span>Theme:</span> dark
=======
        <span>Theme:</span> minimal
>>>>>>> feature/minimal-theme

What do the conflict markers mean?

Git wraps the conflicting area with three special marker lines:

  • <<<<<<< HEAD marks the start of the section from your current branch (main). Everything between this line and ======= is what main has.
  • ======= is the divider between the two versions.
  • >>>>>>> feature/minimal-theme marks the end of the section from the incoming branch. Everything between ======= and this line is what feature/minimal-theme has.

Your job as the reviewer is to delete all three marker lines and keep the version you want.

Resolve the conflict and complete the merge

As the reviewer, you are choosing to keep "dark" as the Theme value. You need to remove all three marker lines and keep only the line you want.

  • In Windsurf, delete the <<<<<<< HEAD line.
  • Delete the ======= line.
  • Delete the >>>>>>> feature/minimal-theme line.
  • Delete the line that says <span>Theme:</span> minimal.
  • Keep the line that says <span>Theme:</span> dark.
  • Save index.html.

The Theme section of your file should now look like this:

    <div class="config-item">
        <span>Theme:</span> dark
    </div>
  • Stage the resolved file by running:
git add index.html
  • Complete the merge by committing with a message that explains your decision:
git commit -m "Merge feature/minimal-theme, keep dark theme"

Why stage and then commit?

Running git add tells Git that you have resolved the conflict in index.html. Running git commit finalizes the merge and creates the merge commit. This two-step process is how every conflict resolution works.

  • Push the resolved merge to GitHub by running:
git push origin main

Before you check the log, what do you expect to see? Two merge commits, one for each feature branch you brought into main.

  • Verify your merge history by running:
git log --oneline

You should see both merge commits in the history. The most recent is your conflict resolution commit ("Merge feature/minimal-theme, keep dark theme") and below it the clean merge of feature/dark-theme.

  • Confirm the Theme value is correct by checking your file. The Theme line in index.html reads "dark".

Still seeing conflict markers?

If git commit failed or your log does not show the merge commit, the conflict was not fully resolved.

  • Open index.html and search for <<<<<<<. If any marker lines remain, delete them manually, save, then re-run git add index.html and git commit.
  • If you want to start the merge over, run git merge --abort to undo the merge attempt and try again from the beginning.

Help me resolve a merge conflict in index.html

✔️ Awesome, I've got everything!

Great. Double check you have saved index.html and pushed to GitHub.

ⓧ I'd like to double check the full code

Here is what index.html on main should look like after both merges:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Team Config Dashboard</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; }
        h1 { color: #333; }
        .config-item { background: #f5f5f5; padding: 12px; margin: 8px 0; border-radius: 4px; }
        .config-item span { font-weight: bold; }
    </style>
</head>
<body>
    <h1>Team Config Dashboard</h1>
    <p>Project: Gavelogy Practice</p>
    <div class="config-item">
        <span>Environment:</span> development
    </div>
    <div class="config-item">
        <span>Theme:</span> dark
    </div>
    <div class="config-item">
        <span>Notifications:</span> enabled
    </div>
</body>
</html>

You just resolved your first merge conflict. You now know exactly what those <<<<<<< and >>>>>>> markers mean, and you can confidently pick the right change when two branches collide. Next up, you will lock down main with branch protection so nobody can push directly to it again.

Lock Down Main and Open a Pull Request

In the previous step, you merged branches directly into main from your terminal. That worked, but it is dangerous on a real project.

Without guardrails, a bad merge can reach production before anyone reviews it. Branch protection forces every change through a Pull Request so you (or a teammate) can review the diff before it hits the main branch.

In this step, get ready to:
  • Lock down main with a branch protection rule that blocks direct pushes.
  • Create a feature branch and push new code to it.
  • Open a Pull Request, review the diff, and squash-merge it.
Add a branch protection rule on GitHub

Branch protection rules live in your repository's settings on GitHub. You will enable one rule that requires all changes to main to go through a pull request first.

  • Open your git-workflow-practice repository on GitHub.
  • Click Settings in the top navigation bar of your repo.
  • In the left sidebar, click Branches.
  • Click Add branch protection rule.
  • In the Branch name pattern field, type main.
  • Check the box for Require a pull request before merging.
  • Leave Require approvals unchecked (since you are working solo, requiring approvals would block you from merging your own PRs).
  • Scroll down and check Do not allow bypassing the above settings. This ensures the rule applies even to you as the repo owner.
  • Click Create at the bottom of the page.

Why enable "Do not allow bypassing"?

By default, GitHub lets repository admins bypass branch protection rules. Since you own this repo, your pushes would still succeed without this setting. Enabling it makes the rule apply to everyone, including you.

Before you run the next command, what do you think will happen if you try to push directly to main?

  • Test the protection by running this command in your terminal:
git push origin main

GitHub rejects the push. You should see an error message mentioning that the protected branch update failed and that changes must be made through a pull request.

Push went through without an error?

If your push succeeded, go back to your repo's Settings > Branches and edit the rule you just created. Make sure Do not allow bypassing the above settings is checked. Without it, admin users can bypass the protection.

Why is my push going through despite branch protection?

Create the feature branch and add the version display

Since you can no longer push directly, you need a feature branch. This is the standard workflow: create a branch, make your change, push the branch, then open a pull request.

  • Create and switch to a new branch by running:
git checkout -b feature/add-version
  • Open index.html in Windsurf.
  • Find the Notifications config item div:
    <div class="config-item">
        <span>Notifications:</span> enabled
    </div>
  • Add the following new config item div directly below it, before the closing </body> tag:
    <div class="config-item">
        <span>Version:</span> 1.0.0
    </div>

What does this add?

This adds a new row to your Team Config Dashboard showing the project version. It uses the same config-item class as the existing rows, so it matches the styling automatically.

  • Save index.html.
  • Stage, commit, and push the branch by running these commands:
git add index.html
git commit -m "feat: add version display"
git push origin feature/add-version

You should see output confirming a new remote branch was created on GitHub.

Seeing "failed to push"?

Make sure you are pushing to feature/add-version (the feature branch), not main. Run git branch to confirm you are on the correct branch. The asterisk shows your current branch.

Help me fix my failed push to the feature branch.

Open, review, and squash-merge the pull request

Your feature branch is now on GitHub. The final step is to open a Pull Request, review the changes, and merge them into main using a squash merge.

  • Go to your git-workflow-practice repository on GitHub.
  • You should see a yellow banner saying your branch had recent pushes. Click Compare & pull request.
  • In the Title field, enter Add version display to config dashboard.
  • In the description box, write a short explanation of what the PR does and why (for example: "Adds a Version config item to the dashboard so the team can see the current release at a glance").
  • Click Create pull request.

What is a Pull Request?

A Pull Request is a proposal to merge one branch into another. It shows the diff (what changed), lets reviewers leave comments, and keeps a record of why the change was made.

On a team, PRs are how code gets reviewed before it reaches production.

  • Click the Files changed tab to see the diff. You should see green highlighted lines showing the new Version div you added.
  • Switch back to the Conversation tab.
  • Click the dropdown arrow on the green merge button and select Squash and merge.
  • Click Confirm squash and merge.
  • When prompted, click Delete branch to clean up the remote feature branch.

Why squash merge?

Squash merge combines all the commits from your feature branch into a single commit on main. This keeps your history clean and readable.

Instead of seeing every tiny commit from a branch, you get one clear entry: "Add version display to config dashboard".

Before you check, what do you expect git log --oneline main to show? Will you see the individual commit from the feature branch, or something different?

  • Pull the merged changes to your local main by running:
git checkout main
git pull origin main
  • Verify the squash merge by running:
git log --oneline

You should see a single squash commit for the version feature (not the individual commits from the branch). On GitHub, the merged PR shows a purple Merged badge.

Don't see the squash commit?

Make sure you ran git pull origin main after the merge. Your local main does not update automatically when you merge on GitHub.

How do I sync my local branch after merging on GitHub?

Your main branch is now protected, and you have completed a full PR workflow. Next up, you will create a PR template that structures every future code review.

Secret mission

Create a PR Template for Structured Reviews

When you review AI-generated PRs on Gavelogy, having a consistent template forces you (and the AI) to document what changed, why, and how to test it. This eliminates guesswork during code review. In this secret mission, you will create a PR template that auto-fills every future pull request with a structured review format.

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, pause them, or delete them entirely.

Resources you used:

  • The git-workflow-practice GitHub repository (public, with branch protection).
  • The local git-workflow-practice folder on your Desktop.

Keep everything running

No action needed. Choose this if you plan to keep practicing branching, conflicts, and PRs in this repo.

  • Your GitHub repository stays available at your GitHub profile.
  • Your local folder remains on your Desktop for future edits.
  • Branch protection stays active on main, so all changes still require a PR.

Pause - I'll come back to this later

Since this project has no running processes or paid services, pausing is the same as keeping. Your repo and local files stay as they are with no cost.

  • No action needed. The GitHub repo and local folder sit idle until you return.

Delete - I don't want to use this again

Remove all project resources and start fresh if you ever want to redo the project.

  • Delete the GitHub repository: go to your repo page, click Settings, scroll to the bottom of the General page, and click Delete this repository in the Danger Zone section. Confirm the deletion by typing the repository name when prompted.
  • Delete the local project folder by running this command in your terminal:

macOS

rm -rf ~/Desktop/git-workflow-practice

Windows

Remove-Item -Recurse -Force ~\Desktop\git-workflow-practice

Nice Work!

Nice Work!

You just completed the full branch-conflict-PR-merge cycle that professional development teams use every day. You now have a protected GitHub repository where every change must pass through a reviewed Pull Request before it reaches main.

You've learned how to:

  • Create feature branches that diverge from the same commit, trigger a real merge conflict, and resolve it by reading Git's conflict markers.
  • Set up branch protection on main so direct pushes are blocked and every change must go through a pull request.
  • Open a Pull Request on GitHub, review the diff, and squash-merge it into a clean single commit on the base branch.
  • Secret Mission: Created a reusable PR template that structures every future code review on Gavelogy.

Ready to quiz yourself?