Back to Blog 中文 EN

Git Workflow ─ The Complete Guide
From Solo to Team

The real reason Git never sticks ─ you're learning "commands," not "workflow." Memorize commands and you forget them; understand the workflow and it serves you for life. This guide breaks the whole thing down, from solo dev to team collaboration ─ branching strategies, PR conventions, rebase vs merge, conflict resolution, and the 10 commands you'll use every day. Cheatsheet included.

Start with 3 mindsets

01 Git isn't a backup tool ─ it's a time machine

A lot of beginners treat Git as "a way to push code to GitHub for backup" ─ that's using a Ferrari as a bicycle.

Git's real value: you can go back to any past state, see "who changed what, when, and why," and experiment on multiple branches at the same time.

02 Branches are free

A Git branch isn't "a copy of the whole project" ─ it's "a pointer to a particular commit." Creating one costs only a few KB.

So ─ don't be afraid to branch. Trying out an idea? Branch. Fixing a bug? Branch. Running a little experiment? Branch.

03 Commits are written for future you

How well you write today's commit message decides whether the you of three months from now can figure out "why this piece of code looks the way it does."

This has nothing to do with "short vs long commits" ─ it's about "whether the person writing it thought about the future."


The simplest workflow for solo dev

If you haven't joined a company yet and you're just practicing on your own, this is enough:

# 1. Set up the repo
git init
git remote add origin [email protected]:user/repo.git

# 2. New feature, new branch ─ every time
git checkout -b feat/login-page

# 3. Write code, commit often (key: committing often beats committing rarely)
git add src/Login.tsx
git commit -m "feat(auth): add login form skeleton"

# 4. Done? Push it up
git push -u origin feat/login-page

# 5. Open a PR on GitHub and review yourself (yes, review your own code)
# 6. Merge back into main

# 7. Delete the local branch
git checkout main
git pull
git branch -d feat/login-page

The key ─ use the PR flow even when it's just you.

Why? Because it forces you to "pause and look at your own code through a third party's eyes." Most bugs you catch yourself the moment you open the PR.


The 3 team workflows

01 GitHub Flow (simplest, most common)

Best for: 80% of web product teams

Structure:

  • main ─ always shippable
  • feat/xxx, fix/xxx ─ short-lived branches

Flow:

  1. Branch off main
  2. Write code and commit on the branch
  3. Push it up and open a PR
  4. A teammate reviews + CI passes
  5. Merge back into main and auto-deploy

Pros: simple, great for continuous deployment.
Cons: there's no intermediate "ready to ship" state.

02 GitLab Flow (good when you need release control)

Best for: products that ship in versions (mobile apps, enterprise software)

Structure:

  • main ─ in development
  • production ─ the live version
  • release/v1.2 ─ a specific release branch

03 Git Flow (complex, for large projects)

Best for: traditional software companies that need a strict release cycle

Structure (the complex one):

  • main ─ live
  • develop ─ in development
  • feature/xxx ─ feature branches
  • release/xxx ─ release prep
  • hotfix/xxx ─ emergency fixes

Pros: rigorous, with a dedicated process for everything.
Cons: too heavy for web products ─ most small teams don't need it.

My advice ─ start with GitHub Flow and level up only when you actually need to.


Branch naming ─ keep it consistent

A naming convention I recommend:

feat/short-description       # new feature
fix/short-description        # bug fix
refactor/short-description   # refactor
docs/short-description       # docs
test/short-description       # tests
chore/short-description      # chores (dependency bumps, config)

# Examples
feat/oauth-google-login
fix/payment-cancel-bug
refactor/extract-pricing-service
docs/update-readme
chore/upgrade-node-20

Why it helps:

  • The branch name alone tells you what it's about
  • GitHub groups similar types together when sorting
  • It matches your commit message type (feat / fix / refactor)

Commit messages ─ a structured format

I recommend the Conventional Commits format:

<type>(<scope>): <subject>

<optional body>

<optional footer>

Example:

feat(auth): add OTP for mobile users

Mobile users were getting locked out due to SMS delivery delays.
Added OTP fallback with 3-minute validity window.

Closes #142

Common types:

  • feat ─ new feature
  • fix ─ bug fix
  • refactor ─ refactor (no behavior change)
  • docs ─ docs
  • test ─ tests
  • chore ─ chores
  • perf ─ performance optimization

The upside of this format ─ tools can auto-generate a changelog and auto-bump the version based on your commits.


Rebase vs Merge ─ when to use which

git merge

Combines two branches, keeps all history, and adds an extra "merge commit."

When to use it:

  • Merging a branch back into main (feat → main)
  • A shared main with multiple people ─ avoid rewriting already-pushed history

git rebase

"Replays" your commits on top of another branch's latest state, turning history into a single straight line.

When to use it:

  • Syncing your feature branch to the latest main before opening a PR
  • Cleaning up commit history (squash / reword)

The key rule

Don't rebase anything you've already pushed.
Because rebase changes the commit hashes, and the history others have already pulled gets scrambled.

So in practice:

  • Personal feature branch (not shared yet) → fine to rebase and clean up
  • Shared branch (main / develop / anything others are on) → always merge

Resolving merge conflicts ─ the 4-step method

A lot of beginners freeze on conflicts ─ but there's actually a fixed process:

Step 1: Fetch the latest first

git fetch origin
git status   # check where things stand

Step 2: Pull the latest main into your branch

# If you haven't pushed yet
git checkout feat/your-branch
git rebase origin/main

# If you've already pushed
git checkout feat/your-branch
git merge origin/main

Step 3: Open your editor and read the conflict markers

The conflicted file will contain:

<<<<<<< HEAD
your version
=======
their version
>>>>>>> origin/main

Keep one side, or combine both, then delete the <<<, ===, and >>> markers.

Step 4: Mark resolved + continue

git add <filename>
git rebase --continue   # if you're using rebase
# or
git commit              # if you're using merge

The key ─ if you're unsure, abort:

git rebase --abort     # cancel the rebase
git merge --abort      # cancel the merge

You'll be back to the state before the conflict ─ think it through, then try again. No one is forcing you to resolve a conflict on the spot.


10 commands you'll use every day

Command What it does
git statusSee your current state and which files changed
git diffSee uncommitted changes
git log --oneline -10See the last 10 commits (compact view)
git add <file>Add changes to staging
git commit -m "msg"Commit (the one you use most)
git checkout -b <branch>Create a new branch and switch to it
git push -u origin <branch>Push a new branch for the first time
git pull --rebasePull the latest with rebase, no merge commit
git stashStash current changes and clear the working dir
git reset --soft HEAD~1Undo the last commit but keep the changes

Advanced moves ─ 5 of them

01 Use git rebase -i to clean up history

Before opening a PR, tidy up your messy commits into clean ones:

git rebase -i HEAD~5   # tidy up the last 5 commits

Inside the editor you can:

  • pick ─ keep it
  • squash ─ merge into the previous one
  • reword ─ change the commit message
  • drop ─ delete this commit

This makes your PR look like a senior wrote it.

02 Use git stash to switch tasks

You're mid-feature and the PM suddenly says "there's a bug in production, fix it now":

git stash                 # stash your current changes
git checkout main
git checkout -b fix/urgent-bug
# fix the bug, merge back into main
git checkout feat/your-feature
git stash pop             # bring back your changes and keep going

Way cleaner than "throwing in a random WIP commit."

03 git bisect to find which commit introduced a bug

You know the code was bug-free a week ago and it's broken today ─ no need to check commits one by one:

git bisect start
git bisect bad             # current state has the bug
git bisect good <commit-from-a-week-ago>
# Git auto-checks out a commit in the middle; you test it
# No bug → git bisect good
# Has bug → git bisect bad
# Git binary-searches its way to the exact commit that introduced the bug

On a big codebase, this command is a lifesaver.

04 Make good use of .gitignore and .gitattributes

A basic .gitignore:

# Node
node_modules/
.env*
!.env.example

# Editor
.vscode/
.idea/
*.swp

# OS
.DS_Store
Thumbs.db

# Build
dist/
build/
*.log

05 Add a pre-commit hook

Run checks automatically before each commit (lint, format, test):

# husky + lint-staged (the most common combo)
npm i -D husky lint-staged
npx husky init

# In .husky/pre-commit, write
npx lint-staged

# In package.json
"lint-staged": {
  "*.{ts,tsx}": ["eslint --fix", "prettier --write"]
}

Once it's set up, every commit gets checked automatically ─ no more "I thought I fixed it but it's broken."


3 scenarios that scare beginners

01 "I committed the wrong thing"

# Not pushed yet
git reset --soft HEAD~1     # undo the commit, keep the changes
# fix it and commit again

# Already pushed
# Don't git push --force (unless it's your own branch)
# Use git revert <commit> to make a "reverse commit"

02 "I worked on the wrong branch"

# Not committed yet
git stash
git checkout <the-right-branch>
git stash pop

# Committed but not pushed
git checkout <the-right-branch>
git cherry-pick <that commit hash>
git checkout <the-wrong-branch>
git reset --hard HEAD~1    # remove the commit from the wrong branch

03 "I accidentally committed my .env"

# Immediately rotate every secret in .env (passwords, API keys)
# Then remove it from history completely

# Use git filter-repo (recommended) or BFG Repo Cleaner
git filter-repo --path .env --invert-paths

# Force push (this rewrites remote history ─ coordinate with the team first)
git push --force

The key ─ don't pretend it's fine. A committed secret is already leaked ─ rotate it immediately, then scrub the history.


One last reminder

Git's value isn't learning the commands ─ it's building the habits.
Before you write code ─ branch.
Every time you finish one small thing ─ commit.
Every commit ─ write a message for future you.

Build these 3 habits for a month and you'll find Git becomes the tool that "lets me change code without fear" ─ because you know any mistake can be undone.

Git isn't something engineers need to learn ─ it's working like an engineer, itself.

Stuck on Git and want someone to walk you through it?

A 30-minute 1-on-1 session for NT$1,500 ─ I'll run you through a full workflow and tackle the conflict / rebase problems your repo is actually stuck on.

Book a session on LINE Subscribe to the newsletter first