Module 2: Git from Scratch for Automators
5. Branches for changing without fear
Description
By the end of this lesson you will be able to experiment with your workflow without risking the version that works. You'll know what a branch is —a parallel line of work— and what HEAD is, you'll handle git branch and git switch to create branches and move between them, you'll know a simple branching model designed for workflows (main as the version ready for production, and feature branches for what you're testing), and you'll merge your work back with git merge, including what happens —and what to do— when a conflict shows up in the JSON.
This matters because it's what gives a team back the freedom to improve. Remember the second cost of Cumbre's "bad Tuesday" from lesson 1: after a scare, people stop touching the workflow out of fear of breaking it. Branches dissolve that fear. They let you try an idea —a new category, a different node, a different routing logic— in a separate space where, if it goes wrong, nothing happened: the good version stays intact, waiting for you. It's the difference between experimenting on a copy and experimenting on the original.
Connection to the module: so far all your work lived on a single timeline, main, one photo after another. This lesson opens a second dimension: parallel lines. You're going to use lesson 4's diffs to review what you did on your branch before merging it, and the branches you learn here are the foundation for two things coming up: shared work on GitHub (lesson 6), where each person works on their own branch, and the iteration across three different branches lesson 8's project asks for. Merging and, when needed, reverting (lesson 7) complete the cycle.
What a branch actually is
The word "branch" already brings a good image: a tree's. A trunk that at some point splits into two branches growing separately. In Git it's almost literal: your history is the trunk, and a branch is a line of development that splits off to grow separately, without affecting the trunk.
But there's a more precise definition, and it's worth having because it defuses the idea that a branch is something heavy or complicated. A branch, in Git, is simply a movable pointer to a commit. Nothing more. It's a label with a name —main, add-priority-routing— pointing at a photo in your history, that moves forward on its own every time you make a new commit.
Think of it with a bookmark. A book is your commit history, page after page. main is a bookmark that says "I'm here." When you read one more page —you make a commit— you move the bookmark to the new page. Creating a branch is putting a second bookmark on the same page, with another name. From that moment on you have two markers; you can advance one without moving the other. That's a branch: an independent bookmark that advances at its own pace.
That's why branches in Git are so lightweight and so fast. They don't copy your project, they don't duplicate files, they don't take up real space. Creating a branch is putting a new label pointing at the commit you're on. It takes a fraction of a second. In other version control systems, "branching" was a heavy operation people avoided; in Git it's so cheap that branching for any experiment is the norm, not the exception.
HEAD: where you're standing
To understand branches you need one more concept, one that already peeked out in lesson 3: HEAD. HEAD is the pointer indicating which branch you're standing on right now. If branches are bookmarks, HEAD is the arrow that says "the bookmark I'm currently using is this one." When git status tells you On branch main, it's telling you HEAD points to main. When you switch branches, what moves is HEAD: it now points to the other branch, and your files in the folder change to reflect that branch's state.
This is the part that surprises people the first time: switching branches changes the files in your folder. If you're on main with the two-category version, and you jump to a branch where you added a third one, your order-triage.json in the folder changes on its own to show that branch's version. It's not magic or risk: Git keeps both states in the history and shows you the one for the branch you're standing on. When you go back to main, the file goes back to main's version. It's like turning the book's bookmark: the page you see is the one for the marker you're using.
git branch and git switch: creating and moving
Two commands do almost all the work with branches. First, it's worth seeing where you are.
To list your branches and see which one you're on:
git branch
What to expect, in a repository that has only used main so far:
* main
The asterisk * marks the branch you're standing on —where HEAD points. With a single branch, there isn't much to see; the asterisk makes sense once there are several.
To create a new branch and switch to it in one step, use git switch with the -c option (for create):
git switch -c add-priority-routing
What to expect:
Switched to a new branch 'add-priority-routing'
You just did two things: you created a branch called add-priority-routing pointing at the same commit you were on, and you moved HEAD toward it. You're now standing on the new branch. Confirm it:
git branch
* add-priority-routing
main
The asterisk moved: you're now on add-priority-routing. And notice something important: main is still there, intact, pointing at where it was. Nothing happened to it. You have two bookmarks on the same page; from here you can advance one without touching the other.
To move between branches that already exist, it's git switch without the -c:
git switch main
Switched to branch 'main'
And to go back to the feature one, git switch add-priority-routing. Simple: -c to create and jump, no -c to jump to one that already exists.
A note on git checkout. If you search for help online, you're going to see a lot of tutorials using git checkout -b name to create branches and git checkout name to move. They do the same as git switch. The reason for the difference is historical: git checkout is an old command that did too many different things —switching branches, restoring files, and more— and that made it confusing and even dangerous. In 2019, Git introduced two new commands that split those tasks: git switch for moving between branches and git restore for restoring files. They're clearer and less error-prone, so in this guide we use switch. If you see checkout elsewhere, now you know it's the same thing with the old name.
A simple branching model for workflows
With the mechanics clear, the real question is how to use branches day to day. There are very elaborate branching strategies for large software development teams —with names, rules, and diagrams— and they're exactly the kind of advanced Git this guide leaves out on purpose. For a workflow's lifecycle, a two-level model is enough, and it's this:
main is the good version, the one ready for production. The golden rule is: main always works. Whatever is on main is what you could promote to production without breaking a sweat. You don't experiment directly on main; you treat it as sacred.
Every change or experiment lives on its own feature branch. When you're about to touch something —add a category, change the routing, try a new node— you create a branch with a name describing the change: add-priority-routing, try-urgent-category, fix-crm-timeout. You work there with total calm, make whatever commits you need, and only once the change is tested and convinces you, you merge it back into main.
Why this model works so well for workflows: it gives you a safe place to experiment. If the try-urgent-category branch turns out to be a bad idea, you delete it and main never found out. If it turns out good, you merge it and main absorbs it. At no point was the version running in production at risk. It's the freedom to try things without fear, with a net underneath.
A practical convention for branch names, just like with commit notes: in English, lowercase, with hyphens, describing the change with a verb. add-priority-routing reads well; my-changes or test2 tells nobody anything. A branch's name, like a commit message, is a message to your team and to your future self.
Worked example: trying out an idea on a branch and merging it
Let's walk through the complete cycle: create a branch, change order-triage on it, review the change with a diff, go back to main, and merge. The idea we're going to try: making order-triage, after classifying, route priority orders through a different step —adding a routing node.
Step 1 — Start from a clean main. Confirm where you are and that there are no loose changes:
git status
It should say On branch main and nothing to commit, working tree clean. Starting clean avoids dragging half-finished changes onto the new branch.
Step 2 — Create the experiment's branch and move to it.
git switch -c add-priority-routing
Switched to a new branch 'add-priority-routing'
You're on safe ground to experiment. Nothing you do here touches main.
Step 3 — Make the change in n8n. Open order-triage, add the routing node for priority orders (for example a Switch node routing based on category), connect it, save, and export. Replace your order-triage.json with the new version.
Step 4 — Review what you changed, with what you learned in lesson 4.
git diff
Read the diff applying the four "read through the noise" rules: skip the versionId and the positions, and stop at the parameters and the new nodes. You should see the routing node added. Reviewing before saving is a good habit: it confirms the change is what you wanted and only that.
Step 5 — Save the change on the branch.
git add order-triage.json
git commit -m "Add priority routing after classification"
[add-priority-routing d4e5f6a] Add priority routing after classification
1 file changed, 14 insertions(+), 2 deletions(-)
Notice the name in brackets: [add-priority-routing ...]. The commit got saved on your feature branch, not on main. Your feature bookmark advanced; main's stayed still.
Step 6 — Check that main didn't find out. Go back to main and look at the file:
git switch main
Switched to branch 'main'
Now open your order-triage.json in the folder —or look at it in n8n by importing it. The routing node isn't there. This is what I promised would surprise you: switching branches, the file in your folder went back to main's version, the one before the experiment. Your work isn't lost: it's still saved on the add-priority-routing branch, waiting. It's just not on main. Confirm it with the history:
git log --oneline
a91c3f8 (HEAD -> main) Add wholesale category to the order classifier
3e5d720 Widen CRM lookup timeout to 15 seconds
c08b4a2 Add order-triage workflow
The Add priority routing commit doesn't show up, because it's not on main. HEAD points to main, and main doesn't have that commit. Everything's in order.
Step 7 — Decide and merge. Suppose you tested the routing, it works, and it convinces you. Time to bring it into main. Standing on main (which is where you want the change to land), run:
git merge add-priority-routing
What to expect:
Updating a91c3f8..d4e5f6a
Fast-forward
order-triage.json | 16 +++++++++++++---
1 file changed, 14 insertions(+), 2 deletions(-)
Read it: main got updated from commit a91c3f8 to d4e5f6a —your branch's. The word Fast-forward deserves an explanation, because you're going to see it often. It means main hadn't moved since you created the branch, so Git didn't have to "combine" two diverging lines: it just advanced main's bookmark until it reached your branch's. Like advancing the trunk's bookmark to the page where the branch's already was. It's the cleanest possible merge.
Now git log --oneline on main does show your commit:
d4e5f6a (HEAD -> main, add-priority-routing) Add priority routing after classification
a91c3f8 Add wholesale category to the order classifier
3e5d720 Widen CRM lookup timeout to 15 seconds
c08b4a2 Add order-triage workflow
main absorbed the work. Notice main and add-priority-routing now point at the same commit —both bookmarks are on the same page again, the new one.
Step 8 — Clean up the branch. Since you already merged the work, the feature branch did its job. You can delete it so you don't pile up old branches:
git branch -d add-priority-routing
Deleted branch add-priority-routing (was d4e5f6a).
You didn't lose anything: the commit lives on main. You deleted the bookmark, not the pages. The -d (for delete) only deletes the branch if it's already merged; if you tried to delete a branch with unmerged work, Git would stop you to protect you —you'd need the capital -D to force it, and that's a command worth thinking twice about.
That's the complete cycle of a branch: create, work, review, merge, clean up. You're going to repeat it for every change worth isolating.
When you want to throw the experiment away
Not every experiment goes well, and that's exactly the point of branches. Suppose the try-urgent-category branch turned out to be a bad idea: the "urgent" category confused the classifier. You don't want to merge it. What do you do? Nothing special: you just go back to main and delete the branch.
git switch main
git branch -D try-urgent-category
Here it is the capital -D, because the branch has work you did not merge and you want to discard it on purpose. Git forces you to use the capital as an "are you sure?" —discarding unmerged work is irreversible through the normal channels. After this, main is exactly as it was before the experiment, as if it never happened. That's the gift of branches: failed experiments leave no scar. You tried, it didn't work, you threw it away, and your good version was never at risk.
Merging when lines diverge: conflicts
The Fast-forward from the example was the easy case, because main didn't move while you worked on your branch. In real work —especially on a team, which is lesson 6— sometimes main does advance while you're on your branch: someone else merged a change. Then the two lines diverged, and merging them requires actually combining them.
Most of the time, Git combines the two lines on its own, without you doing anything: if the changes touched different parts of the file, it merges them automatically and creates a special commit called a merge commit that has two parents. But when both sides changed the same line differently, Git can't guess which one wins. That's a merge conflict, and Git stops and asks you to resolve it.
A conflict looks like this inside the file. Git marks the disputed zone with some special lines:
<<<<<<< HEAD
"text": "=Classify this order into one of: standard, priority, wholesale.",
=======
"text": "=Classify this order into one of: standard, priority, urgent.",
>>>>>>> try-urgent-category
Read it like this: between <<<<<<< HEAD and ======= is your version (the one on the branch you're standing on, main); between ======= and >>>>>>> try-urgent-category is the version from the other branch. Git is telling you "both branches changed this line; tell me which one to keep." Resolving the conflict means editing the file to leave the correct version —maybe one of the two, maybe a combination of both— and deleting the three marker lines (<<<<<<<, =======, >>>>>>>). Then you do git add on the resolved file and git commit to close the merge.
Now, this lesson's honesty: conflicts in an n8n workflow's JSON are especially uncomfortable. Because of the noise you already know —positions, versionId, everything in the same nested structure— a conflict can show up in zones that aren't even real logic, and editing JSON by hand, with its braces and commas, is delicate: one extra comma and the file stops being valid, and n8n won't be able to import it.
That's why, for workflows, the most practical way out of a conflict is often not editing the JSON by hand, but resolving it from n8n: you decide which is the correct version of the workflow, rebuild it or pick it in the editor, export it clean, and replace the conflicting file with that good one. It's less elegant than editing the diff, but for a nested, noisy JSON it's usually safer. And —announcing without solving it— Module 3, by normalizing the JSON, makes conflicts much rarer and much more readable, because it eliminates the noise that causes and magnifies them. For now, the rule is: don't panic at the <<<<<<< markers; identify the two sides, keep the correct one, delete the markers, and if the JSON looks tangled, prefer replacing it with a clean export from n8n.
Seeing the map of your branches
When you have several branches coexisting, it helps to see the whole tree at a glance instead of imagining it. Git can draw the history for you with the branches as lines, right in the terminal:
# --all: every branch, not just the current one.
# --graph: draws the branch lines on the left.
git log --oneline --all --graph
What to expect, with main and a feature branch you haven't merged yet:
* 8b1f0c2 (add-priority-routing) Add priority routing after classification
| * 2d9a4e7 (try-urgent-category) Try an urgent category in the classifier
|/
* a91c3f8 (HEAD -> main) Add wholesale category to the order classifier
* 3e5d720 Widen CRM lookup timeout to 15 seconds
* c08b4a2 Add order-triage workflow
Read it bottom to top. The common trunk is the three commits at the bottom, on main. At a91c3f8 the tree forks: from there two branches come out, add-priority-routing and try-urgent-category, each with its own unmerged commit. The lines and the |/ bar on the left draw that fork. It's the same tree image we opened the lesson with, but real and generated by Git from your own history. When you get lost among branches —and it happens at first— this command reorients you in a second: it shows you where each line split off and what's left to merge.
It's a read-only view: it changes nothing, it just draws. Use it without fear any time you want a map of where your work stands.
Common mistakes
Experimenting directly on main (conceptual). What happens: a risky change gets made without creating a branch first, and if it goes wrong, the good version got contaminated and you have to revert by hand. Why it happens: creating the branch feels like an extra step when "I'm just going to try something quick." How to spot it: if git status says On branch main right before an experimental change, you're about to make this mistake. How to fix it: adopt the reflex of creating a branch before any change you're not sure you want to keep. It's a two-second git switch -c name that buys you the peace of mind that main always works. The model's golden rule: don't experiment on the sacred thing.
Losing track of which branch you're on (practical). What happens: you think you're on your feature branch but you're on main, or the other way around, and you end up committing in the wrong place —a half-finished experiment lands on main, or a change meant for main gets trapped in a branch you later delete. Why it happens: the current branch isn't always in view, and it's easy to forget after a while of working. How to spot it: git status tells you the branch in its first line (On branch ...), and git branch marks it for you with the asterisk. How to fix it: make git status your first command always, before changing or saving anything. Many automators configure their terminal to show the current branch in the prompt, so it's always visible; it's a setting worth adopting once you're comfortable.
Trying to switch branches with unsaved changes (practical). What happens: you have loose changes in the working tree, you try git switch to another branch, and Git stops you with a message saying your local changes would be lost. Why it happens: switching branches rewrites your folder's files to the other branch's version, and Git doesn't want to overwrite your unsaved work. How to spot it: if git switch fails mentioning "your local changes would be overwritten" or "commit your changes," this is it. How to fix it: decide what to do with those changes before jumping —the most common thing is to commit them on the current branch (git add + git commit) and then switch calmly. If you don't want the change, you can discard it with git restore. The lesson: finish or save what you're doing before switching branches.
Panicking at a merge conflict (conceptual). What happens: a git merge comes up with conflicts, the <<<<<<< markers show up in the file, and the reaction is to close everything or believe the repository broke. Why it happens: conflicts look dramatic and n8n's JSON makes them look worse. How to spot it: Git tells you clearly —"Automatic merge failed; fix conflicts and then commit the result"— and git status lists the conflicting files. How to fix it: breathe. A conflict didn't break anything; Git is waiting for your decision. Identify the two sides (yours between <<<<<<< HEAD and =======, the other one below), keep the correct one, delete the three markers, and for a tangled workflow prefer replacing the JSON with a clean export from n8n. Then git add and git commit. If you change your mind halfway and want to cancel the merge entirely, git merge --abort puts you back to the state before you tried it. You're never trapped.
Exercises
Exercise 1 — Walk through a branch's cycle. In your repository, create a branch called try-shorter-timeout, switch to it, change the CRM's timeout in order-triage to 8 seconds, export, and make a commit with the note Reduce CRM lookup timeout to 8 seconds. Then go back to main, confirm with git log --oneline that this commit is not on main, and finally decide: merge it with git merge if it looks good, or delete it with git branch -D if you'd rather discard it. Note which command you used at each step.
See solution
The sequence, if you decide to merge:
git switch -c try-shorter-timeout
# (you make the change in n8n and replace order-triage.json)
git add order-triage.json
git commit -m "Reduce CRM lookup timeout to 8 seconds"
git switch main
git log --oneline # the commit does NOT show up here
git merge try-shorter-timeout
git branch -d try-shorter-timeout
If instead you decide to discard, the last two steps get replaced by: staying on main and running git branch -D try-shorter-timeout (capital, because you're discarding unmerged work).
Why it works: you did the whole cycle with a real decision in the middle. The key step is the git log --oneline from main that does not show your commit: verifying with your own eyes that the branch's work is isolated from main is what installs confidence in the model. main didn't find out about your experiment until you decided to merge it.
Exercise 2 — Predict the file's content. Starting from main with the version of order-triage that has two categories (standard, priority), you do this in order: (1) git switch -c add-third-category, (2) you change the classifier to three categories and commit, (3) git switch main. At this point, which categories does the order-triage.json in your folder have? And if you then run git switch add-third-category?
See solution
After step (3), standing on main, your order-triage.json has two categories. The commit with the third category lives on the add-third-category branch, not on main, and switching back to main your folder's file changed to reflect main's state —which never saw the third category.
If you then run git switch add-third-category, your folder's file changes again, now to three categories, because you're standing on the branch that does have that commit.
Why it works: this is the concept that's hardest the first time —that switching branches rewrites your folder's files— made concrete. The physical file on your disk isn't "the single truth"; it's a projection of whichever branch you're standing on. Once this clicks, branches stop being scary: you understand your work doesn't get erased when you switch branches, it just gets hidden and shown depending on where HEAD points.
Exercise 3 — Resolve a conflict by hand. You're about to merge and this conflict shows up in order-triage.json. The main branch changed the timeout to 15 seconds; the fix-crm branch changed it to 20. After talking it over, the team decided the correct value is 20 seconds. Write how that zone of the file should look once the conflict is resolved.
"options": {
<<<<<<< HEAD
"timeout": 15000
=======
"timeout": 20000
>>>>>>> fix-crm
}
See solution
The resolved zone should look like this, with the chosen value (20000) and none of the three markers:
"options": {
"timeout": 20000
}
After leaving the file like this, you close the merge with:
git add order-triage.json
git commit -m "Merge fix-crm: set CRM lookup timeout to 20 seconds"
The most common mistake here is forgetting to delete one of the markers (<<<<<<<, =======, or >>>>>>>). If even one is left, the file stops being valid JSON —those symbols aren't part of the syntax— and n8n won't be able to import it. That's why, after resolving a conflict, it's worth searching the file for those symbols to confirm none are left.
Why it works: resolving a conflict is, at bottom, making a human decision Git couldn't make for you —which of the two values is correct— and leaving it written down cleanly. This exercise is intentionally simple, with just one disputed line; in a real, noisy JSON you'd rather replace the file with a clean export from n8n, but knowing how to read and clean up the markers by hand is the foundation for understanding what's going on.
Summary and next step
In this lesson you learned to experiment without fear. You understood what a branch is —a movable pointer to a commit, a second bookmark advancing at its own pace, so lightweight that branching for any experiment is the norm— and what HEAD is, the pointer indicating which branch you're standing on and that makes switching branches rewrite your folder's files to that branch's version. You handled git branch to list, git switch -c to create and jump, and git switch to move, noting that git checkout is the same thing with the old name. You adopted a simple model for workflows —main always works, every change on its own feature branch— and walked through the complete cycle: create, change, review with a diff, merge with git merge (seeing Fast-forward as the cleanest merge), and clean up with git branch -d. You saw how to throw away a failed experiment with no scar left behind, and you faced merge conflicts: what the <<<<<<< markers are, how to resolve them, and why in a noisy n8n JSON it's often better to replace the file with a clean export —a problem Module 3 reduces at the root.
Before moving on to lesson 6 you should be able to: create a branch, make a commit on it, and verify main doesn't have it; merge a branch back into main and delete it; and, faced with a conflict, identify the two sides and know git merge --abort always gets you out of trouble.
So far, all your history —with its commits and its branches— lives in one place: your machine. If your disk fails, if you switch computers, if someone else on the team needs to work on order-triage, that history locked up on your machine is useless to them. Lesson 6 opens the door to the world: you're going to push your repository to GitHub, a copy hosted on the internet that backs up your entire history and gives your team a common place to work. You're going to learn what a remote is, how to create a repository on GitHub, and how to sync with git push, git pull, and git clone. And you're going to see why GitHub covers —for free— that "share workflows between users" thing the Community edition of n8n doesn't provide natively. Your branches are ready; now let's give them a shared home.
Resources
- Branches in a Nutshell — Git Docs — the official book's chapter explaining branches as movable pointers and HEAD, with diagrams. This lesson's conceptual foundation.
- Basic Branching and Merging — Git Docs — the cycle of creating, working on, and merging branches, including
fast-forwardand conflict resolution. - git switch — Git Docs — the reference for the modern command for moving between branches, with the
-coption for creating. - git merge — Git Docs — the merge reference, including
--abortfor canceling a merge with conflicts and backing out unharmed. - Source control and environments — n8n Docs — the background on how n8n conceives branches and environments in its native version control; useful for keeping Module 6's Community/Enterprise boundary in mind.