Post

07 - Advanced Workflows and Team Collaboration

07 - Advanced Workflows and Team Collaboration

#07 - Advanced Workflows and Team Collaboration

Overview

This module covers the tools and patterns that separate junior developers from mid-level ones. You will use these every week at work:

  • VSCode Git integration — use the Source Control panel to manage Git without the terminal
  • Stash — save unfinished work when you need to switch tasks
  • Rebase — keep a clean, linear commit history
  • Reflog — recover from mistakes that seem unrecoverable
  • Cherry-pick — apply a specific commit from one branch to another
  • Pull Request workflow — the standard way teams collaborate

Git Stash: Saving Work in Progress

The Problem

You are halfway through building a new feature on feature-dashboard. Your manager calls: “Stop everything, there is a critical bug on production!” You need to switch to main and fix the bug NOW.

But you cannot commit your half-finished dashboard code — it does not work yet. And if you just switch branches, you risk losing your changes or carrying them into the wrong branch.

Stash saves your work temporarily so you can switch branches with a clean slate.

Basic Stash

1
2
3
4
#You are on feature-dashboard, midway through work
echo "half built dashboard" >> dashboard.py
echo "some CSS" >> style.css
git status

Expected:

1
2
3
Changes not staged for commit:
    modified: dashboard.py
    modified: style.css
1
2
3
4
#Save the work without committing
git stash

git status

Expected:

1
nothing to commit, working tree clean

Your changes are saved in a stash. Working directory is clean. Now you can safely switch branches:

1
2
3
4
5
6
7
8
git checkout main
#fix the bug, commit, push
git checkout feature-dashboard

#Get your work back
git stash pop

git status

Expected:

1
2
3
Changes not staged for commit:
    modified: dashboard.py
    modified: style.css

Your work is back exactly as you left it.

If you have multiple stashes, give each one a description so you know what is in it:

1
2
git stash save "WIP: dashboard table and filters — not complete"
git stash save "WIP: user auth form validation — 80% done"

View all stashes:

1
git stash list

Expected:

1
2
stash@{0}: On feature-dashboard: WIP: user auth form validation — 80% done
stash@{1}: On feature-dashboard: WIP: dashboard table and filters — not complete

stash@{0} is the most recent. stash@{1} is the one before that.

Apply vs Pop

1
2
3
4
5
git stash pop          #apply most recent stash AND remove it from the list
git stash apply        #apply most recent stash, but KEEP it in the list

git stash apply stash@{1}   #apply a specific stash by number
git stash pop stash@{1}     #pop a specific stash

Use apply when you want to apply the same stash to multiple branches. Use pop when you are done with it.

Clean Up Stashes

1
2
git stash drop stash@{0}   #delete one stash
git stash clear            #delete ALL stashes (careful!)

What Gets Stashed?

By default, git stash saves:

  • Modified tracked files
  • Staged files

It does NOT save:

  • Untracked files (new files Git has never seen)
  • Files in .gitignore

To include untracked files:

1
git stash --include-untracked

VSCode Git Integration

VSCode has a built-in Source Control panel that lets you run most Git operations without touching the terminal. It is especially useful for stashing, staging individual lines, and visualising branch history.

Opening the Source Control Panel

Press Ctrl+Shift+G (Windows/Linux) or Cmd+Shift+G (Mac), or click the branch icon in the left Activity Bar.

You will see:

  • Changes — modified files (equivalent to git status)
  • Staged Changes — files added with git add
  • Source Control Graph — branch and commit history (bottom of the panel)

Stashing via VSCode

To stash your changes:

  1. Open the Source Control panel (Cmd+Shift+G)
  2. Click the ... (More Actions) menu at the top of the panel
  3. Select Stash → Stash
  4. Type a label for your stash (e.g. WIP: dashboard table) and press Enter

Your working directory is now clean — the same as running git stash save "WIP: dashboard table" in the terminal.

To apply or pop a stash:

  1. Click the ... (More Actions) menu
  2. Select Stash → Pop Stash (applies and removes) or Apply Stash (applies and keeps)
  3. Choose the stash from the list

To view all stashes:

  1. Click the ... menu → Stash → Apply Stash
  2. VSCode shows a dropdown of all stashes with their labels — same as git stash list

Staging Individual Lines (Hunk Staging)

This is something VSCode does that the basic terminal cannot do easily.

  1. Open a modified file
  2. Click the gutter (the coloured bar on the left of the editor) next to the changed lines
  3. Right-click and choose Stage Selected Ranges

You can stage just 3 lines of a 50-line change and leave the rest unstaged. Useful for keeping commits focused and clean.

Switching Branches via VSCode

Click the branch name in the bottom-left status bar. A dropdown appears showing all local branches. Click one to switch — same as git checkout <branch>.

To create a new branch: click the branch name → Create new branch.

GUI vs CLI Comparison

Action Terminal VSCode
See changed files git status Source Control panel (Cmd+Shift+G)
Stage a file git add <file> Click + next to the file
Stage specific lines git add -p (interactive) Right-click lines → Stage Selected Ranges
Commit git commit -m "message" Type message in box → click ✓
Stash git stash save "label" ... menu → Stash → Stash
Pop stash git stash pop ... menu → Stash → Pop Stash
Switch branch git checkout <branch> Click branch name in status bar
Create branch git checkout -b <branch> Click branch name → Create new branch
Push git push Click sync icon in status bar
Pull git pull Click sync icon in status bar
View log / history git log --oneline --graph Source Control Graph (bottom of panel)

When to use which:

  • VSCode GUI — staging individual lines, visualising history, quick branch switches, day-to-day stash operations
  • Terminal — interactive rebase, cherry-pick, reflog recovery, scripting, anything the GUI does not expose

Most professional developers use both — GUI for the visual feedback, terminal for precise control.

Resolving Merge Conflicts in VSCode

This is where VSCode genuinely beats the terminal. Instead of editing raw conflict markers (<<<<<<<, =======, >>>>>>>), VSCode gives you a visual 3-way editor.

When a conflict occurs, open the conflicted file. VSCode highlights each conflict block with four clickable options:

  • Accept Current Change — keep your version (HEAD)
  • Accept Incoming Change — keep the incoming version (the branch being merged)
  • Accept Both Changes — keep both, one after the other
  • Compare Changes — open a side-by-side diff view

Click the option that makes sense for each conflict block. You can also manually edit the result below the conflict markers if neither option is exactly right.

After resolving all conflicts:

  1. Save the file
  2. In the Source Control panel, the file will move from Merge Changes to Changes
  3. Stage it by clicking +
  4. Click the ✓ to commit — VSCode pre-fills the merge commit message for you

The 3-way merge editor (VSCode 1.69+):

For complex conflicts, click Resolve in Merge Editor at the bottom of the file. This opens a full three-panel view:

  • Left panel — your changes (Current)
  • Right panel — incoming changes (Incoming)
  • Bottom panel — the final result you are building

You can accept individual hunks from either side and edit the result panel directly. This is the clearest way to handle conflicts when both sides have valid changes that need combining.

Install these before your first day at work — most professional teams use them:

GitLens (most important)

Install from the Extensions panel (Cmd+Shift+X) → search “GitLens”.

What it adds:

  • Inline blame — hover over any line to see who wrote it, when, and in which commit
  • File History — right-click any file → Open File History to see every commit that touched it
  • Commit details on hover — see the full commit message and diff without leaving the editor
  • Branch and stash visualisation — richer view than the built-in Source Control panel

GitLens is free for individual use and installed by default at many companies. Get familiar with it early.

Git Graph (optional but useful)

Search “Git Graph” in Extensions. Adds a visual tree of your entire branch history — useful when working with multiple branches and wanting to see how they relate.


Git Rebase: Keeping a Clean History

The Problem Rebase Solves

You create feature-login from main. While you work, your teammates merge 3 new commits into main. Now your history looks like:

1
2
3
main:          A  B  C  D  E
                     \
feature-login:        F  G  H

When you eventually merge feature-login into main, Git creates a merge commit and the history shows a fork. With many features and many developers, history looks like a tangled web.

Rebase replays your commits on top of the latest main, creating a clean, linear history:

1
2
3
4
After rebase:
main:          A  B  C  D  E
                                  \
feature-login:                     F'  G'  H'

Now when you merge, it is a fast-forward — no merge commit, perfectly linear history.

Basic Rebase

1
2
3
4
5
6
7
8
#You are on feature-login
#Meanwhile main got new commits

git checkout main
git pull           #get latest commits on main

git checkout feature-login
git rebase main    #replay feature-login commits on top of latest main

Expected (if no conflicts):

1
Successfully rebased and updated refs/heads/feature-login.

View the clean result:

1
git log --oneline --graph --all

Expected — your feature commits now sit on top of the latest main commit, linear history:

1
2
3
4
5
6
* h9i0j1k (feature-login) Add login redirect on success
* g8h9i0j Add login form validation
* f7g8h9i Add login page HTML
* e6f7g8h (main) Latest teammate commit
* d5e6f7g Previous teammate commit
...

Handling Conflicts During Rebase

If your commits conflict with commits on main, rebase stops and asks you to resolve each conflict one at a time:

1
2
3
4
CONFLICT (content): Merge conflict in auth.py
error: could not apply f7g8h9i... Add login page HTML
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".

Fix conflicts, then continue:

1
2
3
4
5
6
7
8
#1. Open the conflicted file(s), resolve, save
#2. Mark as resolved
git add auth.py

#3. Continue the rebase (NOT git commit)
git rebase --continue

#Repeat if the next commit also has a conflict

If you want to give up and go back to how things were:

1
git rebase --abort

Rebase vs Merge: When to Use Which

Situation Use
Updating a feature branch with latest main Rebase
Cleaning up local commits before pushing Rebase
Merging a completed feature into main Merge
On a shared branch others are using Merge (never rebase shared branches)
Preserving the full “what happened when” history Merge

The golden rule: Never rebase commits that have already been pushed to a shared branch.Rebase rewrites commit hashes, so if someone else has your old commits and you rebase, their history and yours diverge and cause chaos.

Safe to rebase: local commits you have not pushed yet, or commits on a branch only you are using.

Interactive Rebase: Cleaning Up Commits

Before submitting a pull request, your commit history might look messy:

1
2
3
4
5
f3c2b1a Fix typo
e2b1a0f More fixes
d1a0b9e WIP save
c0b9a8e actually fix the bug
b9a8b7f Add login feature

With interactive rebase, you can squash, reword, and reorder commits before anyone else sees them:

1
git rebase -i HEAD~5   #open an interactive editor for last 5 commits

Git opens vi with this:

1
2
3
4
5
6
7
8
9
10
11
pick b9a8b7f Add login feature
pick c0b9a8e actually fix the bug
pick d1a0b9e WIP save
pick e2b1a0f More fixes
pick f3c2b1a Fix typo

#Commands:
#p, pick   = keep this commit
#r, reword = keep commit, edit the message
#s, squash = combine with the commit ABOVE it
#d, drop   = delete this commit entirely

Change it to squash the messy commits into one clean one:

1
2
3
4
5
pick b9a8b7f Add login feature
squash c0b9a8e actually fix the bug
squash d1a0b9e WIP save
squash e2b1a0f More fixes
squash f3c2b1a Fix typo

Save and close. Git opens another editor to write the combined commit message:

1
2
#Write a new combined message:
Add login feature with form validation and redirect

Save and close. Result: one clean commit instead of five messy ones.


Git Reflog: Your Safety Net

What is Reflog?

Reflog is a log of every time HEAD moved. Every checkout, every reset, every commit, every merge — all recorded. It is a safety net that lets you recover from things that seem permanent.

1
git reflog

Expected:

1
2
3
4
a1b2c3d (HEAD -> main) HEAD@{0}: commit: Fix login redirect bug
9f0e1d2 HEAD@{1}: reset: moving to HEAD~1
b3c4d5e HEAD@{2}: commit: Add login feature
7e8f9a0 HEAD@{3}: checkout: moving from feature-login to main

Recovering from an Accidental Hard Reset

This is one of the most common “I destroyed my work” scenarios:

1
2
3
4
5
6
7
8
9
10
11
12
#Make some commits
echo "important work" > work.py
git add work.py && git commit -m "Important feature"

echo "more important work" >> work.py
git add work.py && git commit -m "Second part of feature"

#Now accidentally do a hard reset
git reset --hard HEAD~2

git log --oneline
#The commits appear gone!

But they are NOT gone. Check reflog:

1
git reflog

Expected:

1
2
3
4
7e8f9a0 HEAD@{0}: reset: moving to HEAD~2
b3c4d5e HEAD@{1}: commit: Second part of feature
a1b2c3d HEAD@{2}: commit: Important feature
...

Find the commit you want to recover (b3c4d5e) and reset to it:

1
2
3
4
git reset --hard b3c4d5e

git log --oneline
#Your commits are back!

Recovering a Deleted Branch

1
2
3
4
5
6
7
git checkout -b important-work
echo "critical feature" > critical.py
git add critical.py
git commit -m "Critical feature - do not delete"

git checkout main
git branch -D important-work   #oops!

Recover it:

1
2
3
4
5
git reflog
#HEAD@{1}: commit: Critical feature - do not delete   that commit hash

git checkout -b important-work <commit-hash>
#Branch is restored with all its commits

Reflog is only on your local machine.It is not pushed to remote. But for local mistakes, it is almost always your rescue route.


Git Cherry-Pick: Applying Specific Commits

The Problem

You fixed a critical bug on your feature-api branch but the fix needs to go to main immediately — without merging the entire unfinished feature.

Cherry-pick applies a specific commit from one branch to another.

1
2
#On feature-api, find the bug fix commit
git log --oneline feature-api

Expected:

1
2
3
f5e4d3c Add API rate limiting (unfinished)
e4d3c2b Fix null pointer crash in user lookup    this is the bug fix
d3c2b1a Start API refactor
1
2
3
#Switch to main and apply just that one commit
git checkout main
git cherry-pick e4d3c2b

Expected:

1
2
[main 9a8b7c6] Fix null pointer crash in user lookup
 1 file changed, 3 insertions(+), 1 deletion(-)

The bug fix is now on main without any of the unfinished API work.

Cherry-Pick with Conflicts

If the cherry-picked commit conflicts with main:

1
2
3
4
5
6
#Resolve the conflict in the file
git add <conflicted-file>
git cherry-pick --continue

#Or abort:
git cherry-pick --abort

Pull Request / Merge Request Workflow

This is the most important section in Week 2. Every single code change you make at work will go through this process. A Pull Request (PR) — called Merge Request (MR) on GitLab — is a formal request to merge your branch into main. It enables:

  • Code review before changes reach production
  • Discussion about design decisions
  • Automated testing (CI/CD) before merging
  • A paper trail of why changes were made

The Complete PR Workflow

Step 1: Start from latest main

1
2
3
git checkout main
git pull                        #ensure you are up to date
git checkout -b feature-user-auth

Step 2: Build the feature with clean commits

1
2
3
4
5
6
7
8
#Work on the feature...
echo "auth logic" > auth.py
git add auth.py
git commit -m "Add JWT token generation"

echo "tests" > auth_test.py
git add auth_test.py
git commit -m "Add tests for JWT token generation"

Step 3: Update your branch with any new main commits

1
2
3
git fetch origin
git rebase origin/main          #or: git merge origin/main
#Resolve any conflicts

Step 4: Push your branch

1
git push -u origin feature-user-auth

Step 5: Open the PR on GitHub GitHub will show a banner: “Your recently pushed branch: Compare & pull request”. Click it.

Write a good PR description:

1
2
3
4
5
6
7
8
9
10
11
12
13
## What this PR does
Implements JWT-based authentication for the login endpoint.

## Why
Replaces the old session-based auth which was causing timeout issues (#42).

## How to test
1. POST to /api/login with valid credentials  get a token
2. Use the token in the Authorization header for protected routes
3. Try with an expired token  should get 401

## Notes
JWT secret is in .env — do not commit that file.

Step 6: Address review comments

1
2
3
4
5
#Reviewer asks you to add error handling
echo "better error handling" >> auth.py
git add auth.py
git commit -m "Add error handling for expired tokens per review feedback"
git push

The PR automatically updates — reviewers see the new commit without you needing to create a new PR.

Step 7: After approval, merge the PR On GitHub, choose your merge strategy:

  • Squash and merge(most common) — all commits squashed into one clean commit on main
  • Merge commit— all commits preserved with a merge commit
  • Rebase and merge— commits replayed linearly, no merge commit

Step 8: Clean up locally

1
2
3
git checkout main
git pull                        #get the merged commit
git branch -d feature-user-auth #delete the local branch

Code Review: How to Give and Receive Feedback

As PR Author

Before requesting review, review your own PR first:

  • Read every line of your diff
  • Check: does this PR do exactly one thing?
  • Check: are there any debug logs or commented-out code?
  • Check: does every commit message make sense?

When responding to feedback:

  • Thank reviewers for catching things
  • Explain your reasoning if you disagree (don’t just quietly make the change)
  • Mark conversations as resolved once you have addressed them

As Code Reviewer

Give feedback that explains the WHY:

1
2
3
4
5
6
7
8
9
10
11
 Bad: "Change this"
 Good: "We use const instead of let for values that don't change — it makes it 
         clear to readers that this variable is not reassigned. Can you update this?"

 Bad: "This is wrong"  
 Good: "This will throw a TypeError if user is null (which can happen when the 
         session expires). What if we add a null check here?"

 Bad: "I don't like this"
 Good: "This works, but our convention in this codebase is to use named exports
         instead of default exports. See how auth.js does it?"

Acknowledge good work too:

1
2
 "Nice use of the factory pattern here — much cleaner than what we had before."
 "Good catch adding the test for the edge case."

Summary

Command What it does
git stash Save work in progress temporarily
git stash save "label" Stash with a description
git stash list View all stashes
git stash pop Apply and remove most recent stash
git stash apply stash@{N} Apply a specific stash
git rebase main Replay current branch on top of main
git rebase -i HEAD~N Interactive rebase: edit last N commits
git rebase --continue Continue after resolving rebase conflict
git rebase --abort Cancel rebase entirely
git reflog See all HEAD movements (recovery tool)
git cherry-pick <hash> Apply one specific commit to current branch
git push -u origin <branch> Push branch + open a PR
This post is licensed under CC BY 4.0 by the author.