GitVibes / Advanced Workflows
Part 5

Advanced Scenarios: Managing a Multi-Branch Workflow

"Real projects are messy. Stash your work, resolve your conflicts, and keep moving."

By now you know the fundamentals. But real-world development is rarely linear — you'll be mid-feature when a critical bug drops, your branch will diverge from a teammate's, and two files will clash during a merge. These advanced tools handle the chaos.

Note
As you grow, you'll often work on multiple tasks at once. Your AI-driven workflow will be interrupted by urgent bugs or questions. Git provides the tools to manage this context-switching seamlessly.

5.1 "I Need to Switch Branches, but My Work Isn't Ready"

You're deep in a feature branch with ten modified files when your manager says "urgent bug on main." You can't commit half-finished work, and you can't lose it either. The stash is your escape hatch.

Stash your work-in-progress to switch context without losing anything
Note
The Problem: You're in the middle of a complex AI refactor with 10 modified files. Your manager says: "Urgent bug on main!" You can't commit half-baked work, and Git may block you from switching branches if your uncommitted changes conflict with the target branch.

The stash is a temporary, private holding area for your dirty changes.

Stash, fix, and return
# 1. Stash your changes (-u includes brand-new files)
git stash push -u -m "WIP: refactoring pipeline, AI changes"

# 2. Fix the urgent bug
git switch main
git pull
git switch -c hotfix/urgent-bug
# ... fix, test, commit, push, create PR ...

# 3. Return to your work
git switch feature/A
git stash pop
Warning
The untracked-files gotcha: a plain git stash only saves changes to tracked files. Brand-new files the AI just created — never committed, never staged — get left behind in the working tree. Add -u (--include-untracked) to take them along — that's also why VS Code's menu has a separate "Stash (Include Untracked)" item.

git stash pop re-applies your changes and removes them from the stash. Use git stash apply to keep the stash entry for reuse.

In VS Code, you can do all of this without memorizing commands. Open the ... menu in the Source Control panel -- you'll see a Stash submenu with all the options you need:

VS Code
The ... menu includes a Stash submenu with 'Stash (Include Untracked)' and 'Pop Latest Stash' -- everything you need for context-switching.

Choose "Stash (Include Untracked)" to save all your work. When you're ready to come back, go to ... → Stash → "Pop Latest Stash" to restore everything exactly where you left off.

Try It: The Stash Workflow

You're mid-refactor on feature/A when a critical bug comes in. Stash your work, fix the bug on a hotfix branch, then come back and pop the stash.
Stash: Context-Switch Safely

Loading playground...

Vibe it

"I need to switch branches but I'm not done here — save my work temporarily"

"Stash my current changes, switch to main to fix a bug, then come back and restore them"

5.2 "My Branch is Out of Date" (Rebase vs. Merge)

Your feature branch has been alive for a few days and main has moved on without you. Now you need to catch up — and Git offers two philosophies with very different trade-offs.

Merge preserves history, rebase rewrites it — choose based on your team's convention
Note
The Problem: Your feature branch is "stale." main has moved on. There are two philosophies for updating it.

You have two options for catching up with main, and each tells a different story in your commit history.

git merge main

Creates a new "Merge Commit" on your branch. Preserves the exact history -- messy but 100% accurate.

History: "Worked on feature... merged main... worked on feature..."

git rebase main

"Replays" your commits on top of the latest main. Creates a clean, linear history as if you started today. The replayed commits are re-created — same changes, brand-new commit ids.

History: main's commits, then copies of yours (the ' marks: same change, new id) — all in a straight line.

Caution
The Golden Rule of Rebasing: Never rebase a public branch (one your team is also using). Because rebase re-creates the commits with new ids, everyone else's copy of the branch still points at the old ones — you've rewritten a history they're standing on.

VS Code supports both approaches. Use the ... menu in Source Control → "Pull (Rebase)" to rebase instead of merge when pulling. For merging, use the Command Palette (Cmd+Shift+P / Ctrl+Shift+P) → "Git: Merge Branch..." and select the branch to merge.

Tip
The AI-First Developer's Choice: Since your experiment branch is your private playground, rebase is preferred to keep it clean before creating a PR. It avoids cluttering the PR with "I merged main" commits.
Important
The push after a rebase. If your branch was already on GitHub, the rebase just re-created commits the remote still has the old versions of — so a plain git push gets rejected. The correct follow-up is git push --force-with-lease — the safe force from section 4.6 — and it's only OK because this is your branch. Rebase, lease-push, open the PR: that's the full ritual.

One config gem while you're here: git config --global rebase.autostash true makes Git stash your uncommitted work automatically before a rebase (or pull --rebase) and pop it after — dissolving the "Git blocked my switch" problem 5.1 opened with, for the rebase case at least.

Try It: Merge vs. Rebase

Your feature branch and main have diverged. Try git merge main first, then reset and try git rebase main to compare the resulting history.
Merge vs. Rebase

Loading playground...

Vibe it

"My branch is behind main — rebase my changes on top of the latest main"

"Update my feature branch with the latest changes from main using rebase"

Rebase has one more trick: git rebase -i (interactive) lets you rewrite your own branch's history commit by commit — squash five "wip" commits into one, reword a sloppy message, drop an experiment entirely. It's how a messy working branch becomes a clean, reviewable PR. The Golden Rule applies double here: only ever on commits that haven't been shared.

Try It: Squash the WIP

Your branch works but its history is three wip: commits. Run git rebase -i main, reword the first commit to something worthy, and squash the other two into it.
Interactive Rebase: Clean the History

Loading playground...

Vibe it

"Squash my wip commits on this branch into one commit with a proper message"

"Clean up this branch history with an interactive rebase before I open the PR"

5.3 "We Both Edited the Same File" (Merge Conflicts)

This is the moment every developer dreads the first time — and handles calmly by the tenth. Two people changed the same lines, and Git needs a human to decide which version wins.

When two edits collide, Git asks you to choose — this is a merge conflict
Warning
The Problem: You run git pull (or merge main into your branch) and Git halts with CONFLICT. You and a teammate edited the same lines. Git needs you, the human, to resolve it.

Don't panic -- conflicts look intimidating at first, but they follow a simple pattern. Git inserts special markers into your file to show you exactly where the disagreement is.

The Conflict Markers

What you'll see in src/model.py
<<<<<<< HEAD
x = 10
# AI refactor
=======
x = 5
# teammate fix
>>>>>>> main

Everything between <<<<<<< HEAD and the ======= divider is your side (here, the AI's refactor on your branch). Everything below the divider is the incoming side — the label after >>>>>>> names where it came from — the branch you merged (like main here; after a pull it can be a commit id or a longer label instead).

Delete all the markers (<<<, ===, >>>) and edit the code to be the correct final version, then stage and commit. In the playground, you can write the resolved file with echo 'x = 10' > src/model.py.

Not ready to deal with it? There's an eject button here too: git merge --abort cancels the merge and returns your branch to exactly how it was before you ran git merge — the same guilt-free escape you'll meet again with rebase (5.5) and cherry-pick (5.4).

The VS Code Way (The Superior Way)

Editing conflict markers by hand works, but VS Code makes the whole process much more visual and less error-prone.

Tip
This is one of the best features of the IDE. Open a conflicted file and VS Code highlights each block inline, with clickable links right above it: "Accept Current" | "Accept Incoming" | "Accept Both". For tangled, overlapping conflicts, click "Resolve in Merge Editor" to open the full 3-way view:

Left Pane: "Incoming" (teammate's changes)
Right Pane: "Current" (your changes)
Bottom Pane: "Result" (what will be saved)
VS Code
VS Code highlights conflicts inline with clickable actions: Accept Current Change, Accept Incoming Change, or Accept Both Changes.

For complex conflicts with multiple overlapping changes, click "Resolve in Merge Editor" to open the full 3-way view. This gives you the most control over the final result:

VS Code
The 3-way Merge Editor: Incoming changes (left), your changes (right), and the final result (bottom). Use checkboxes to select which changes to keep.

Try It: Resolving a Merge Conflict

The scenario starts mid-merge with conflict markers in src/model.py. Use echo to overwrite the file, then git add and git commit to finish.
Merge Conflict Resolution

Loading playground...

Vibe it

"I have a merge conflict in model.py — help me resolve it, keeping both changes"

"Show me the conflicts and suggest the best resolution for each one"

5.4 Cherry-Pick — Take Only the Gems

Your AI experiment branch is a mess — half-finished rewrites, abandoned TODOs, dead ends. But buried in the middle is one brilliant commit: a currency rounding fix that actually works. You don't want the branch. You want that one commit.

Cherry-pick copies exactly one commit onto your branch and leaves the rest behind
Note
The Problem: An experiment branch has one valuable fix buried among junk commits. Merging would bring everything. You want to extract a single commit.

git cherry-pick copies one commit's changes onto your current branch as a new commit — same change, same message, but a brand-new hash, because it now sits on a different parent. The original commit stays untouched on its branch.

Pick the gem, leave the junk
git log --oneline --all      # Find the gem's hash on the experiment branch
git switch main              # Stand on the branch that should receive it
git cherry-pick e4f5a6b      # Copy exactly that commit here
git log --oneline            # The fix is on main — the junk is not

For an audit trail, add -x: it appends "(cherry picked from commit e4f5a6b...)" to the message, so anyone reading main later can trace where the fix came from. Perfect for the reject-the-PR workflow below.

When to prefer it over merging: merge when the whole branch is worth keeping; cherry-pick when only part of it is. And because a cherry-picked commit replays changes onto code that may have moved on, conflicts can happen here too — the escape hatches mirror rebase exactly:

If the pick conflicts
# Fix the conflicted file, then:
git add <file>
git cherry-pick --continue

# Or walk away as if nothing happened:
git cherry-pick --abort
Tip
The AI reviewing strategy — "reject the PR, cherry-pick the gems": when an agent's branch is 80% noise, don't agonize over salvaging it. Close the PR, cherry-pick the one or two commits that earned their place, and delete the branch. This pairs perfectly with the guidance in section 6.1 on teaching your AI to work in small, single-purpose commits — small commits are what make cherry-picking possible.

Try It: Cherry-Pick the Gem

The experiment branch has a half-finished dashboard rewrite and one gem: a currency rounding fix. Use git log --oneline --all to find it, then git cherry-pick it onto main — and check src/billing.py to confirm the fix arrived.
Cherry-Pick: Take Only the Gem

Loading playground...

Vibe it

"The experiment branch is mostly junk but the rounding fix is good — cherry-pick just that commit onto main"

"Find the commit that fixed the login bug on the old branch and apply only that one here"

5.5 When Rebase Goes Wrong — Conflicts, Continue, Abort

Section 5.2 sold you on rebase for clean history — but it skipped the scary part. Your feature branch tuned src/config.py, main changed the same lines, and halfway through the rebase Git slams the brakes. Here's how to read the wreck and drive out of it.

A paused rebase is a fork in the road: fix and continue, or abort and go home
Note
The Problem: You ran git rebase main and Git stopped with CONFLICT. The rebase is half-done and you're not sure whether to fix it or flee.

Remember what rebase does: it replays your commits one at a time on top of the latest main. If a replayed commit touches lines that main also changed, Git can't guess the winner — so it pauses mid-replay and hands you the keys. Run git status to see the paused state: it reports a rebase in progress and lists the conflicted files under "unmerged paths". The files contain the conflict markers you learned to read in section 5.3 — with one crucial twist.

Warning
The sides are swapped during a rebase. Git rebuilds your branch by standing on main and replaying your commits onto it — so <<<<<<< HEAD is main's version, and the bottom block is your own commit arriving as the "incoming" change. Exactly backwards from a merge. This is the single most famous rebase trap — read the labels, not the positions.
What you'll see in src/config.py — note who is who
<<<<<<< HEAD
TIMEOUT = 10     # main's version (HEAD during a rebase!)
=======
TIMEOUT = 120    # your commit, being replayed
>>>>>>> a1b2c3d (feat: raise the worker timeout)

From here, it's a three-step ritual — the same one every time:

The three-step ritual
# 1. Fix the file — remove the markers, keep the right code
echo 'TIMEOUT = 120' > src/config.py

# 2. Tell Git the conflict is resolved
git add src/config.py

# 3. Resume the replay
git rebase --continue

If more commits remain, Git keeps replaying — and may pause again on the next conflict. Just repeat the ritual until the rebase completes. And if at any point you're lost, confused, or late for dinner, there's a guilt-free eject button:

The guilt-free escape
git rebase --abort   # Everything returns EXACTLY as it was before the rebase
Important
--abort is the reason rebasing your local branches is always safe to attempt. The rebase doesn't touch your original commits until it finishes — abort at any pause and your branch is restored exactly as it was. The Golden Rule from 5.2 still stands (never rebase shared branches), but on your own branch, the worst case is typing one command and being back where you started.
Tip
Resolving the same conflict twice? Never again: git config --global rerere.enabled true turns on reuse recorded resolution — Git remembers how you resolved each conflict and replays the answer the next time the identical conflict appears. On a long-lived AI branch that gets rebased onto main every day, this is the difference between resolving the agent's conflict once and resolving it every single morning.

Try It: Survive a Rebase Conflict

Your feature/tuning branch raised the timeout in src/config.py, but main lowered the same line. Run git rebase main, then cat src/config.py to read the conflict markers. Fix the file with echo, then git add and git rebase --continue. Try git rebase --abort too — watch everything snap back.
Rebase: Resolve, Continue, or Abort

Loading playground...

Vibe it

"My rebase stopped on a conflict in config.py — resolve it keeping the higher timeout and continue"

"This rebase is a mess — abort it and get my branch back to how it was"

5.6 Tags & Releases — Naming Your Milestones

Branches move — every commit drags them forward. But some moments deserve a permanent name: the commit you shipped, the version that passed the audit, the last known-good state before a big AI refactor. That's what tags are: labels that stick to one commit, forever.

Tags are permanent name plates on commits — branches move, tags stay

Git has two kinds of tags, and the choice matters more than it looks:

Lightweight: git tag v1.1.0

Just a name pointing at a commit. No author, no date, no message. Fine for private bookmarks.

Annotated: git tag -a v1.1.0 -m "..."

A full object with author, date, and message. Always annotate releases — future-you will want to know who cut it and why.

Naming convention: most projects use semantic versioningMAJOR.MINOR.PATCH. Bump MAJOR when you break existing users (v1.x → v2.0.0), MINOR when you add features compatibly (v1.0 → v1.1.0), and PATCH for pure bug fixes (v1.1.0 → v1.1.1). One glance at a version tells users how scared to be.

Cut and inspect a release
git tag -a v1.1.0 -m "Release: import support"   # Tag HEAD
git tag                                          # List all tags
git log --oneline                                # Tags appear as decorations
git show v1.0.0                                  # Inspect what a release points at

One surprise: in real Git, git push does not send tags. You push them explicitly — git push origin v1.1.0 for one tag, or git push origin --tags for all of them — though beware: --tags throws every tag at the remote, including private bookmarks like the pre-refactor checkpoint below. git push --follow-tags is the discerning version: it sends only annotated tags on commits you're pushing — which is exactly why this section told you to annotate releases and keep bookmarks lightweight. Need to remove a tag? git tag -d v1.1.0 deletes it locally — and if you deleted one by mistake, just re-tag the same commit (find it with git log --oneline, or fetch the tag back from the remote if you'd already pushed it).

Tip
The AI-first habit: tag before letting an agent loose on a big refactor — git tag -a pre-refactor -m "Last known good before agent rewrite". If things go sideways, a named restore point beats scrolling through the reflog trying to remember which HEAD@{n} was the good one.

Try It: Cut a Release

v1.0.0 shipped two commits ago and the import feature is ready. Tag the current commit with git tag -a v1.1.0 -m "Release: import support", list your tags, then use git show v1.1.0 and git log --oneline to see what each release points at.
Tags: Cut a Release

Loading playground...

Vibe it

"Tag the current commit as v2.0.0 with a message summarizing what's in this release"

"Before you start the refactor, create an annotated tag so we can get back to this exact state"

Challenge: Rescue the Buried Fix

Loading challenge...