Module 02 · Branching & Merging
- Explain what a Git branch is and why branches exist
- Create, switch between, and delete branches using git switch and git branch
- Follow the feature branch workflow to add the Search Agent to the A2A system
- Understand the difference between merging and rebasing
- Identify, understand, and resolve a merge conflict
- Explain why pushing directly to main is dangerous on a shared project
Background
In Module 01, you committed directly to main. That works fine when you are
the only person working on a project — but the moment a second person opens
the same repository, committing directly to main becomes a collision waiting
to happen.
Branches solve this. A branch is a parallel line of development that stays
isolated from main until you’re ready to merge it back. You can make fifty
commits on a branch, break things, experiment, rewrite — and main is
completely unaffected the entire time.
This module has a moment you need to experience to really understand: a merge conflict. You’re going to create one on purpose, read the conflict markers Git leaves in your file, and resolve it manually. Merge conflicts look scary the first time. By the end of this module they’ll just be a thing you fix.
The A2A project currently has one Specialist Agent: the Echo Agent. In this
module you’ll create a feature/specialist-agent-search branch, add the
Search Agent, register it in the Orchestrator’s agent registry, and merge the
branch back into main. The merge conflict exercise uses two diverging versions
of the agent registry — a realistic scenario that happens any time two people
add a new agent at the same time.
Concepts
What Is a Branch?
A branch is a lightweight, movable pointer to a commit. When you create a
branch, Git doesn’t copy any files — it simply creates a new pointer that starts
at the same commit you’re currently on. As you make new commits on the branch,
that pointer moves forward independently of main.
Before branching:
A ── B ── C ← main, HEAD
After creating feature/specialist-agent-search:
A ── B ── C ← main \ \ ← feature/specialist-agent-search, HEAD
After two commits on the feature branch:
A ── B ── C ← main \ D ── E ← feature/specialist-agent-search, HEADmain hasn’t changed at all. Someone else could be making commits to main
while you work on your feature branch — those changes won’t touch your branch
until you explicitly merge or rebase.
git switch vs. git checkout
Git has two ways to work with branches. This course uses git switch, which
was introduced in Git 2.23 specifically for branch operations. It’s cleaner and
less likely to surprise you than the older git checkout.
# Create a new branch and switch to itgit switch -c feature/specialist-agent-search
# Switch to an existing branchgit switch main
# See all local branches (* marks the current one)git branch
# See local and remote branchesgit branch -aThe Feature Branch Workflow
The feature branch workflow is the pattern used by the vast majority of open-source and professional projects. The steps are always the same:
1. Start from an up-to-date main git switch main git pull origin main
2. Create a feature branch git switch -c feat/your-feature
3. Make commits on the feature branch ... work, work, work ... git add . git commit -m "feat: ..."
4. Push the branch to GitHub git push origin feat/your-feature
5. Open a Pull Request (Module 03) ... review, approval ...
6. Merge into main git switch main git merge feat/your-feature
7. Delete the branch git branch -d feat/your-feature git push origin --delete feat/your-featureStep 5 — the Pull Request — is the subject of Module 03. In this module you’ll do the full cycle, but you’ll merge locally instead of via a PR. Module 03 adds the review and approval layer on top.
Merge vs. Rebase
When it’s time to bring your branch back into main, you have two main options:
git merge creates a new “merge commit” that joins the two branch histories
together. The original branch commits are preserved exactly as you made them.
Before merge:
A ── B ── C ← main \ D ── E ← feature branch
After: git merge feature/...
A ── B ── C ─────── M ← main (M is the merge commit) \ / D ── E ← feature branchgit rebase replays your branch commits on top of main as if you had
branched off from the latest commit. The history looks linear — no merge commit.
Before rebase:
A ── B ── C ← main \ D ── E ← feature branch
After: git rebase main (from feature branch)
A ── B ── C ── D' ── E' ← feature branch (D and E replayed as D', E')| Merge | Rebase | |
|---|---|---|
| History | Preserved as-is, shows branching | Linear, cleaner log |
| Merge commit | Yes | No |
| Safe on shared branches | ✅ Yes | ⚠️ Only on your own branches |
| Good for | Feature branches, PRs | Keeping a local branch current |
What Is a Merge Conflict?
A merge conflict happens when two branches have each made changes to the same lines of the same file and Git can’t automatically decide which version to keep.
Git pauses the merge and marks the conflicting sections in your file:
<<<<<<< HEAD "echo": os.getenv("ECHO_AGENT_URL", "http://localhost:8001"),======= "echo": os.getenv("ECHO_AGENT_URL", "http://localhost:8001"), "search": os.getenv("SEARCH_AGENT_URL", "http://localhost:8002"),>>>>>>> feature/specialist-agent-searchReading these markers:
- Everything between
<<<<<<< HEADand=======is what’s in your current branch (main) - Everything between
=======and>>>>>>> feature/specialist-agent-searchis what’s in the branch being merged in - Your job: edit the file to the version you want, remove all three marker lines, stage and commit
Exercise
Part 1 — Prepare Your Branch
-
Make sure you’re on
mainand fully up to date:Terminal window cd ~/github-for-ai-builders/starter-project/pythongit switch maingit pull origin main -
Check your current branch:
Terminal window git branchYou should see
* main— the asterisk marks the active branch. -
Create the feature branch:
Terminal window git switch -c feature/specialist-agent-search -
Confirm you switched:
Terminal window git branchNow
* feature/specialist-agent-searchis active andmainis listed below it. -
Look at the commit history — both branches currently point at the same commit:
Terminal window git log --oneline --graph
Part 2 — Add the Search Agent
-
Inspect the Search Agent file that’s already in the project — it was written in Module 00’s setup but never committed on a feature branch:
Terminal window cat agents/search/main.pyRead through the file. Notice the
mock_search()function and theMOCK_RESULTSdictionary — this is the Search Agent’s “knowledge base” for now. -
Stage and commit the Search Agent:
Terminal window git add agents/search/main.pygit commit -m "feat(search-agent): add mock search specialist agentImplements a keyword-based mock search agent that returnspredefined results for known topics (pull requests, git branches,github actions, codespaces, gitignore).Mock implementation keeps Module 02 focused on branching conceptsrather than API credential management. Real search integrationis a suggested Capstone extension." -
Now register the Search Agent in the Orchestrator. Open
orchestrator/main.pyand find theAGENT_REGISTRYdictionary:AGENT_REGISTRY: dict[str, str] = {"echo": os.getenv("ECHO_AGENT_URL", "http://localhost:8001"),}Add the
searchentry so it reads:AGENT_REGISTRY: dict[str, str] = {"echo": os.getenv("ECHO_AGENT_URL", "http://localhost:8001"),"search": os.getenv("SEARCH_AGENT_URL", "http://localhost:8002"),} -
Commit the registry update:
Terminal window git add orchestrator/main.pygit commit -m "feat(orchestrator): register search agent in routing table" -
Also update
.env.exampleto document the new variable. Open it and add the Search Agent URL below the Echo Agent URL:Terminal window SEARCH_AGENT_URL=http://localhost:8002Commit it:
Terminal window git add .env.examplegit commit -m "chore(config): add SEARCH_AGENT_URL to env example" -
Look at your branch history now:
Terminal window git log --oneline --graphYou should see your three new commits sitting above the original project history, all on
feature/specialist-agent-search.
Part 3 — Test the Search Agent
Before merging, verify that what you just added actually works.
-
Open two terminal tabs. In the first, start the Search Agent:
Terminal window python agents/search/main.py -
In the second terminal, send a test request directly to the agent:
Terminal window curl -X POST http://localhost:8002/run \-H "Content-Type: application/json" \-d '{"task": "search", "input": "What is a pull request?"}'You should see a JSON response with
"status": "success"and a description of pull requests in the"output"field. -
Now test routing through the Orchestrator. In a third terminal, start the Orchestrator:
Terminal window python orchestrator/main.py -
Route a search task through the Orchestrator:
Terminal window curl -X POST http://localhost:8000/run \-H "Content-Type: application/json" \-d '{"task": "search", "input": "How does GitHub Actions work?"}'The Orchestrator should route to the Search Agent and return a result. The journey: your request → Orchestrator (8000) → Search Agent (8002) → response back through the Orchestrator → your terminal. ✅
Part 4 — Push the Branch
-
Push the feature branch to GitHub:
Terminal window git push origin feature/specialist-agent-search -
Navigate to your fork on GitHub:
https://github.com/YOUR-USERNAME/git-github-security-learning -
GitHub will show a banner: “feature/specialist-agent-search had recent pushes — Compare & pull request”. Don’t click it yet — we’ll use the PR workflow in Module 03. For now, just observe that GitHub detected your branch automatically.
-
Click the branch dropdown (it shows
mainby default) and selectfeature/specialist-agent-search. Browse your commits. Notice thatmainstill shows the original files — your changes are completely isolated on the feature branch.
Part 5 — Create a Merge Conflict (On Purpose)
This is the important part. You’re going to simulate what happens when two people add different agents at the same time.
-
Switch back to
main:Terminal window git switch main -
Make a change to the same lines you just modified on the feature branch. Pretend you’re a second developer who just added a “calculate” agent directly on
main:Terminal window code orchestrator/main.pyFind the
AGENT_REGISTRYdictionary (it still shows onlyechoonmain) and edit it to add a calculate agent:AGENT_REGISTRY: dict[str, str] = {"echo": os.getenv("ECHO_AGENT_URL", "http://localhost:8001"),"calculate": os.getenv("CALCULATE_AGENT_URL", "http://localhost:8003"),} -
Commit this change to
main:Terminal window git add orchestrator/main.pygit commit -m "feat(orchestrator): register calculate agent" -
Now look at the diverged history:
Terminal window git log --oneline --graph --allYou should see two branches diverging from the same point —
mainhas the calculate agent,feature/specialist-agent-searchhas the search agent. Neither knows about the other’s change. -
Attempt the merge:
Terminal window git merge feature/specialist-agent-searchGit will output something like:
Auto-merging orchestrator/main.pyCONFLICT (content): Merge conflict in orchestrator/main.pyAutomatic merge failed; fix conflicts and then commit the result.This is not an error. This is Git doing exactly what it should — pausing and asking you to make a decision it can’t make for you.
Part 6 — Resolve the Conflict
-
Check the current state:
Terminal window git statusorchestrator/main.pyappears under “both modified” — this is the conflicted file. -
Open the conflicted file:
Terminal window code orchestrator/main.py -
Find the conflict markers. They’ll look like this:
AGENT_REGISTRY: dict[str, str] = {<<<<<<< HEAD"echo": os.getenv("ECHO_AGENT_URL", "http://localhost:8001"),"calculate": os.getenv("CALCULATE_AGENT_URL", "http://localhost:8003"),======="echo": os.getenv("ECHO_AGENT_URL", "http://localhost:8001"),"search": os.getenv("SEARCH_AGENT_URL", "http://localhost:8002"),>>>>>>> feature/specialist-agent-search} -
Decide what the final version should be. In this case, both agents should be registered — we want
mainto include all three:AGENT_REGISTRY: dict[str, str] = {"echo": os.getenv("ECHO_AGENT_URL", "http://localhost:8001"),"search": os.getenv("SEARCH_AGENT_URL", "http://localhost:8002"),"calculate": os.getenv("CALCULATE_AGENT_URL", "http://localhost:8003"),}Remove all three marker lines (
<<<<<<<,=======,>>>>>>>). None of them should appear in the final file. -
Save the file and confirm no markers remain:
Terminal window grep -n "<<<<<<\|=======\|>>>>>>" orchestrator/main.pyThis command should return no output. If it returns anything, you missed a marker.
-
Stage the resolved file:
Terminal window git add orchestrator/main.py -
Complete the merge:
Terminal window git commit -m "merge: combine search and calculate agent registrationsResolved conflict in AGENT_REGISTRY between:- feature/specialist-agent-search (added search agent)- main (added calculate agent)Both agents are now registered. The calculate agent is aplaceholder — implementation PR to follow." -
Look at your history now:
Terminal window git log --oneline --graphYou’ll see the merge commit (marked with a
\and/junction in the graph) joining the two branch histories together. This is a merge commit — the permanent record that these two lines of development were reconciled.
Part 7 — Clean Up
-
Now that the feature branch is merged, delete it locally:
Terminal window git branch -d feature/specialist-agent-searchThe
-dflag only deletes branches that have been fully merged. Git will refuse if you haven’t merged. Use-D(capital) to force delete an unmerged branch — but be careful, that work is gone. -
Delete the remote branch on GitHub:
Terminal window git push origin --delete feature/specialist-agent-search -
Confirm the local branch list is clean:
Terminal window git branchOnly
mainshould remain. -
Push the updated
mainto GitHub:Terminal window git push origin main -
Refresh your fork on GitHub and confirm the feature branch is gone from the branch dropdown. The commits from the branch now live in
main’s history, accessible via the Commits tab.
Merge Strategies at a Glance
You’ll encounter these options when merging PRs on GitHub in Module 03:
| Strategy | What it does | When to use |
|---|---|---|
| Merge commit | Creates a merge commit, preserves full branch history | Default — keeps complete audit trail |
| Squash and merge | Collapses all branch commits into one commit on main | When branch has many WIP commits you don’t want in main’s history |
| Rebase and merge | Replays branch commits on top of main linearly | When you want a linear history without merge commits |
This repository’s PR template will tell you which strategy to use for each contribution type. When in doubt, merge commit is the safest choice.
Branch Commands Reference
# Create and switchgit switch -c feature/my-branch # Create new branch and switch to itgit switch main # Switch to existing branch
# Listgit branch # Local branchesgit branch -a # Local + remote branchesgit branch -v # Branches with their latest commit
# Renamegit branch -m old-name new-name # Rename a local branch
# Deletegit branch -d feature/my-branch # Delete merged branch (safe)git branch -D feature/my-branch # Force delete (careful!)git push origin --delete branch # Delete remote branch
# Comparegit log main..feature/my-branch # Commits on feature not yet in maingit diff main...feature/my-branch # Changes on feature since branching off main
# Update a branch with latest main (without merging)git fetch origingit rebase origin/main # Replay your branch commits on top of latest mainSummary
In this module you:
- Learned that a branch is a lightweight pointer to a commit, not a copy of files
- Used
git switch -cto create thefeature/specialist-agent-searchbranch and worked entirely in isolation frommain - Added the Search Agent to the A2A system and registered it with the Orchestrator — the system now routes two task types
- Tested the full routing chain: client → Orchestrator → Search Agent → response
- Created a merge conflict on purpose by simulating two developers adding different agents to the same registry at the same time
- Read and resolved conflict markers manually, keeping both agents in the final result
- Cleaned up by deleting the feature branch locally and on GitHub after merging
- Understood the difference between merge, squash and merge, and rebase and merge
Merge conflicts are one of the things developers fear most before they’ve resolved one. Now you’ve done it deliberately, which means the next time one appears unexpectedly you’ll recognize it immediately and know exactly what to do.
What’s Next
Module 03 · Pull Requests & Code Review →
You’ll open your first Pull Request, write a PR description that communicates intent clearly, request a review, and learn what security-aware code review looks like for an AI agent project.