Senior 7 min · March 06, 2026

Force Push Wiped Commits — Version Control Best Practices

Bare git push --force on main can silently delete teammates' committed work.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Git snapshots your project with every commit – think of it as a save point with a note.
  • One commit = one logical change; never mix unrelated changes in a single commit.
  • Branches isolate work: never commit directly to main, always use feature branches.
  • Use merge for shared branches (preserves history), rebase for private branches (clean linear history).
  • Keep branches short-lived (1-2 days) to avoid nightmare merges.
  • Write commit messages in imperative mood: "Fix login bug" not "Fixed login bug" or "fixes".
Plain-English First

Imagine you're writing a 30-page school essay in Google Docs. Every time you finish a paragraph, Google secretly saves a snapshot — so if you accidentally delete three pages, you can rewind to yesterday's version in seconds. Version control is exactly that snapshot system, but for code. Instead of Google doing it automatically, you decide when to save a snapshot, what to name it, and who else can see it. Every professional software team on Earth uses this — and the habits you build around it will define how trustworthy you look as a developer.

Every software project eventually becomes a time machine problem. You ship a feature on Monday, a bug report lands on Wednesday, and by Friday you genuinely can't remember what the code looked like before you touched it. Without a disciplined approach to version control, that's not a inconvenience — it's a crisis. Teams lose work, bugs get shipped twice, and developers overwrite each other's changes without ever knowing it happened. This is not a rare edge case. It happens on real projects, at real companies, every single week.

Version control solves this by giving every change a permanent address in history. Every time you save a meaningful checkpoint (called a commit), the tool records exactly what changed, who changed it, and when. You can compare any two points in history, undo a disastrous change in seconds, and let ten developers work on the same codebase simultaneously without stepping on each other's toes. The tool most teams use today is Git — but the practices around Git are what separate senior engineers from people who just know the commands.

By the end of this article you'll understand what a commit really is and how to write one that makes sense six months later, how to use branches to work safely without breaking anything, how to think about merging versus rebasing, and the three common mistakes that silently wreck team codebases. You don't need any prior experience — just an appetite to build habits that will make every team you join immediately trust your work.

What a Commit Actually Is — And Why Tiny Commits Win Every Time

A commit is a permanent snapshot of your project at a specific moment. Think of it like a save point in a video game — except each save point also has a label you wrote, explaining exactly what changed and why. The label is called a commit message, and it's more important than most beginners realise.

The golden rule is: one commit, one logical change. Not one commit per day. Not one giant commit when the feature is done. One commit per idea. If you fixed a login bug and also tweaked a button colour, those are two separate commits — even if you did both in the same five minutes. Why? Because six months later, when a colleague hunts down the bug that the button-colour change introduced, they need to be able to isolate it instantly.

A good commit message follows this structure: a short subject line (under 50 characters) that completes the sentence 'If applied, this commit will...' — followed by a blank line and an optional body explaining why, not what. The diff already shows what changed. The message should explain the reasoning a future developer won't have access to.

Small, focused commits are also much easier to review in a pull request, much easier to revert if something goes wrong, and they make git blame (a command that shows who last touched each line) genuinely useful instead of pointing at one massive commit that changed 800 lines.

good_commit_workflow.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# --- STEP 1: Check which files you've changed ---
git status
# Output shows modified files — review this before staging anything

# --- STEP 2: Stage ONLY the files related to one logical change ---
# Bad habit: git add .  (stages everything at once — loses granularity)
# Good habit: stage file by file, or by hunk
git add src/auth/login_validator.py
# Only staging the login fix — not the button colour change

# --- STEP 3: Confirm exactly what you're about to commit ---
git diff --staged
# Shows line-by-line what is staged — always read this before committing

# --- STEP 4: Write a commit message that explains WHY, not just WHAT ---
git commit -m "Fix login validator rejecting valid emails with plus signs"
# Subject line: under 50 chars, imperative mood, no full stop at the end

# --- STEP 5: Stage the second unrelated change as its own commit ---
git add src/components/submit_button.css
git commit -m "Update submit button colour to match new brand guidelines"

# --- STEP 6: View the clean, readable history you just created ---
git log --oneline
# Output:
# a3f9c12 Update submit button colour to match new brand guidelines
# 7e8b401 Fix login validator rejecting valid emails with plus signs
# 1d2a9ff Add password strength indicator to registration form
Output
On branch feature/login-improvements
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/auth/login_validator.py
[feature/login-improvements a3f9c12] Fix login validator rejecting valid emails with plus signs
1 file changed, 3 insertions(+), 1 deletion(-)
[feature/login-improvements 7e8b401] Update submit button colour to match new brand guidelines
1 file changed, 2 insertions(+), 2 deletions(-)
a3f9c12 Update submit button colour to match new brand guidelines
7e8b401 Fix login validator rejecting valid emails with plus signs
1d2a9ff Add password strength indicator to registration form
Pro Tip: Use git add -p for surgical staging
Run git add -p (patch mode) to stage individual chunks within a single file — not the whole file at once. This is the move when you've made two unrelated changes in the same file and want to split them into separate commits. Interviewers who dig into Git deeply will be impressed if you mention this unprompted.
Production Insight
Tiny commits make git bisect fast — you can pinpoint the exact commit that introduced a bug.
If a commit touches 30 files, bisecting is useless.
Rule: if you can't write a single-line summary, split the commit.
Key Takeaway
One commit = one logical change.
A good commit message explains why, not what.
Always review staged changes before committing.

Branching Strategy — How to Work Without Breaking Everything

A branch is a parallel universe for your code. The main branch (usually called main or master) represents the code that's live, working, and trusted. A feature branch is a copy of that universe where you can experiment, break things, and rebuild them — without touching the version everyone else is relying on.

The core idea is simple: never commit directly to main. Every piece of work — no matter how small — gets its own branch. You work there, you test there, you get it reviewed there, and only then does it merge back into main. This keeps main in a permanently deployable state, which is the entire point.

Branch names should be descriptive and follow a consistent pattern. A common convention is type/short-description — for example: feature/user-profile-page, bugfix/cart-total-rounding-error, or hotfix/payment-gateway-timeout. This tells every teammate at a glance what type of work is happening and what it's about, without opening a single file.

Keep branches short-lived. A branch that lives for three weeks becomes a nightmare to merge because the main branch has moved on. Aim to open a pull request within a day or two of starting a branch. If a feature is too large to finish that quickly, break it into smaller deliverable pieces — that's a design skill, not just a Git skill, and it signals seniority.

branching_workflow.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# --- Always start from an up-to-date main branch ---
git checkout main
git pull origin main
# Pulling first ensures your new branch starts at the latest point,
# not a stale snapshot from yesterday

# --- Create and immediately switch to your feature branch ---
git checkout -b feature/user-profile-avatar-upload
# The -b flag creates the branch AND switches to it in one step
# Naming convention: type/kebab-case-description

# --- Do your work, committing in small logical chunks ---
git add src/profile/avatar_uploader.py
git commit -m "Add image format validation to avatar uploader"

git add src/profile/avatar_uploader.py
git commit -m "Add file size limit of 5MB for avatar uploads"

git add tests/profile/test_avatar_uploader.py
git commit -m "Add unit tests for avatar upload validation rules"

# --- Push your branch to the remote so teammates can see it ---
git push -u origin feature/user-profile-avatar-upload
# The -u flag sets the upstream tracking — after this you just use 'git push'

# --- Check the current state of your branches ---
git branch -a
# Output:
#   main
# * feature/user-profile-avatar-upload
#   remotes/origin/main
#   remotes/origin/feature/user-profile-avatar-upload

# --- When the pull request is approved, delete the branch cleanly ---
git checkout main
git pull origin main
git branch -d feature/user-profile-avatar-upload
# -d (lowercase) only deletes if it's already been merged — a safe guard
Output
Switched to a new branch 'feature/user-profile-avatar-upload'
[feature/user-profile-avatar-upload 4c1e8a3] Add image format validation to avatar uploader
1 file changed, 14 insertions(+)
[feature/user-profile-avatar-upload 9f2d7b1] Add file size limit of 5MB for avatar uploads
1 file changed, 5 insertions(+), 1 deletion(-)
[feature/user-profile-avatar-upload b3a0c55] Add unit tests for avatar upload validation rules
1 file changed, 38 insertions(+)
Branch 'feature/user-profile-avatar-upload' set up to track remote branch.
main
* feature/user-profile-avatar-upload
remotes/origin/main
remotes/origin/feature/user-profile-avatar-upload
Deleted branch feature/user-profile-avatar-upload (was b3a0c55).
Watch Out: Long-lived branches are technical debt
Every day your branch stays open, main is moving forward without you. After two weeks, merging your branch can feel like defusing a bomb — conflict after conflict, context you've forgotten. The fix isn't to merge faster carelessly; it's to make branches smaller. If a feature takes three weeks, it should probably be three separate branches merged one at a time.
Production Insight
Long-lived branches are the #1 cause of merge conflict nightmares.
Every day your branch lags behind main, the delta grows.
Rule: keep branches under 2 days or break the feature into smaller pieces.
Key Takeaway
Never commit to main.
Branch names should follow type/description.
Short-lived branches keep history clean and conflicts minimal.

Merge vs Rebase — Choosing the Right Way to Combine Work

Once your feature branch is ready, you need to bring it back into main. There are two ways to do this: merge and rebase. They both achieve the same end result — your code ends up in main — but they create very different histories, and understanding the difference is a genuine mark of seniority.

A merge takes both branches and creates a new 'merge commit' that ties them together. History is preserved exactly as it happened — parallel work looks parallel in the log. It's honest, non-destructive, and safe for branches that other people are also working on. The downside is that a project with lots of branches and merges can produce a git log that looks like a tube map — hard to read linearly.

A rebase replays your branch's commits on top of the latest main, one by one, as if you had started your branch today instead of a week ago. The history comes out perfectly linear — no merge commits, no diverging lines. It's much easier to read. The downside: rebase rewrites commit hashes, which means if anyone else has your branch checked out, their history will conflict. The rule of thumb is: never rebase a branch that other people are working on.

The most common professional workflow is: rebase your feature branch on top of main before opening a pull request (to keep history clean), then use a regular merge (or a 'squash merge') when the pull request is approved. This gives you the readability of rebase with the safety of merge at the critical moment.

merge_vs_rebase.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# ============================================================
# SCENARIO: Your feature branch is behind main by 3 commits.
# You want to update your branch before opening a pull request.
# ============================================================

# --- Option A: MERGE (preserves full history, safe for shared branches) ---
git checkout feature/search-filter-improvements
git merge main
# Git creates a merge commit that joins the two histories
# Your log will show a 'Merge branch main into feature/...' commit
# Safe to use when teammates are also on this branch

# --- Option B: REBASE (clean linear history, only for your own branches) ---
git checkout feature/search-filter-improvements
git rebase main
# Git temporarily removes your commits, fast-forwards to latest main,
# then replays your commits on top one by one
# Your commit hashes CHANGE — never do this on a shared branch

# --- If a conflict occurs during rebase, Git pauses and tells you ---
# CONFLICT (content): Merge conflict in src/search/filter_engine.py
# Step 1: Open the file, resolve the conflict markers manually
# Step 2: Stage the resolved file
git add src/search/filter_engine.py
# Step 3: Continue the rebase (NOT git commit — git rebase --continue)
git rebase --continue
# Step 4: If you want to abandon the whole rebase and go back to before
git rebase --abort

# --- After rebase, push requires --force-with-lease (NOT --force) ---
git push --force-with-lease origin feature/search-filter-improvements
# --force-with-lease is safer than --force:
# it refuses to overwrite if someone else has pushed to the branch since your last fetch

# --- View how clean the rebased log looks vs a merged log ---
git log --oneline --graph
# Rebased output (clean, linear):
# * d9f3e11 Add price range filter to search results
# * c7a2b04 Add category multi-select to search sidebar
# * 8e1f9a0 (origin/main, main) Add pagination to product listing
Output
First, rewinding head to replay your work on top of it...
Applying: Add category multi-select to search sidebar
Applying: Add price range filter to search results
* d9f3e11 Add price range filter to search results
* c7a2b04 Add category multi-select to search sidebar
* 8e1f9a0 (origin/main, main) Add pagination to product listing
* 3b7c5d2 Fix checkout total display on mobile
* 1a4e8f6 Add order confirmation email template
Interview Gold: The golden rule of rebase
Never rebase commits that exist on a public or shared branch. If you rebase and force-push a branch that a teammate has checked out, their local copy will have completely different commit hashes for the same changes — causing confusion and lost work. The phrase to memorise: 'Rebase your local work, merge the public work.'
Production Insight
Rebasing a shared branch leads to chaos – teammates' local histories diverge.
Always use --force-with-lease to avoid accidentally overwriting others' work.
Rule: rebase local, merge public.
Key Takeaway
Merge preserves history, rebase rewrites it.
Never rebase a branch that others have checked out.
Use --force-with-lease when you must force push.
When to Merge vs Rebase
IfBranch is shared (multiple developers working on it)
UseUse merge – never rebase shared branches.
IfBranch is private (only you work on it)
UseUse rebase to keep a clean linear history before opening a PR.
IfYou need to preserve the exact timeline of when branches diverged
UseUse merge – rebase makes everything look sequential.
IfYou're bringing a feature branch up to date with main
UseRebase your feature branch onto main (if private), then merge into main via PR.

.gitignore, README, and the Habits That Make Teammates Love You

The practices covered so far — clean commits, short-lived branches, thoughtful merging — are the big ones. But there's a set of smaller habits that separate developers who 'know Git' from developers who 'use Git professionally'. These habits are often what interviewers probe for when they ask 'tell me about your version control workflow.'

First: every repository needs a .gitignore file before the first commit. This file tells Git which files to completely ignore — things like compiled binaries, log files, API keys stored in .env files, and IDE configuration folders like .idea/ or .vscode/. Committing these files is at best noise and at worst a security disaster. The website gitignore.io generates ready-made .gitignore files for any language or framework.

Second: never commit credentials. Not even for a second. Even if you delete them in the next commit, they are permanently in Git history and can be extracted. Use environment variables or secret management tools instead. If you accidentally commit a secret, rotate the credential immediately — assume it's compromised.

Third: write a meaningful README. It should answer four questions: what does this project do, how do I run it locally, how do I run the tests, and who do I contact if something is broken. A project with a clear README signals a professional codebase. A project without one signals chaos.

Finally: tag your releases. When code goes to production, run git tag -a v1.4.0 -m "Release 1.4.0 — adds avatar upload and search filters". Tags create permanent, named markers in history so you can always check out exactly what was running in production on any given day.

professional_repo_setup.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# ============================================================
# Setting up a professional repository from scratch
# ============================================================

# --- 1. Initialise the repository ---
mkdir ecommerce-platform
cd ecommerce-platform
git init

# --- 2. Create a .gitignore BEFORE your first commit ---
cat > .gitignore << 'EOF'
# Python compiled files — not needed in version control
__pycache__/
*.pyc
*.pyo

# Virtual environment — each developer creates their own
venv/
.env/

# Environment variables — NEVER commit secrets
.env
.env.local
.env.production

# IDE configuration — personal to each developer's setup
.idea/
.vscode/
*.swp

# Build output — regenerated from source, not source itself
dist/
build/
*.egg-info/

# OS files
.DS_Store
Thumbs.db

# Log files — these grow forever and belong nowhere near git
logs/
*.log
EOF

# --- 3. Create a professional README ---
cat > README.md << 'EOF'
# Ecommerce Platform

A Python-based ecommerce backend with product search, cart management, and Stripe payments.

## Running Locally

```bash
python -m venv venv
source venv/bin/activate   # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env       # Fill in your own values
python manage.py runserver
```

## Running Tests

```bash
pytest tests/ -v
```

## Contact

Owner: platform-team@yourcompany.com
EOF

# --- 4. First commit with both essential files ---
git add .gitignore README.md
git commit -m "Initial project setup with gitignore and README"

# --- 5. Tag a release when code hits production ---
git tag -a v1.0.0 -m "Release 1.0.0 — initial launch with product listing and cart"
git push origin v1.0.0
# Now this exact state of the code is permanently labelled

# --- 6. Verify the tag exists ---
git tag -l
# Output: v1.0.0

git show v1.0.0 --stat
# Shows the commit the tag points to, the tag message, and files changed
Output
Initialized empty Git repository in /projects/ecommerce-platform/.git/
[main (root-commit) 2a9c4f1] Initial project setup with gitignore and README
2 files changed, 28 insertions(+)
create mode 100644 .gitignore
create mode 100644 README.md
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 1.02 KiB | 1.02 MiB/s, done.
Total 3 (delta 0)
To github.com:yourteam/ecommerce-platform.git
* [new tag] v1.0.0 -> v1.0.0
v1.0.0
tag v1.0.0
Tagger: Your Name <you@yourcompany.com>
Date: Wed Nov 8 14:32:01 2023 +0000
Release 1.0.0 — initial launch with product listing and cart
commit 2a9c4f1
2 files changed, 28 insertions(+)
.gitignore | 22 ++++++++++++++++++++++
README.md | 6 +++++++++++++++++++
Watch Out: Secrets in git history live forever
Deleting a committed API key in a follow-up commit does NOT remove it from history. Anyone with a clone of the repo can run git log -p and see every version of every file ever committed. If you commit a secret, treat it as fully compromised — rotate it immediately, then use a tool like git filter-repo to scrub the history if it's a private repo. If it's a public repo, assume the key has already been harvested by automated scanners.
Production Insight
A missing .gitignore can double your repo size with build artifacts.
Committing .env files is a data breach waiting to happen.
Rule: .gitignore before first commit, and never commit secrets.
Key Takeaway
.gitignore before first commit.
Secrets in git history are forever compromised.
Tags make releases reproducible.

Handling Merge Conflicts Like a Pro

Merge conflicts happen when two branches modify the same part of the same file in different ways. Git can't decide which version to keep, so it stops and asks you to resolve it manually. This is not a sign of failure — it's a normal part of collaborative development. But how you handle conflicts separates a smooth workflow from a chaotic one.

When a conflict occurs, Git marks the conflicted file with special markers: <<<<<<<, =======, and >>>>>>>. The section between <<<<<<< and ======= is your current branch's version; between ======= and >>>>>>> is the incoming branch's version. You edit the file to produce the correct final state, remove the markers, and stage the file.

The most common mistake during conflict resolution is to blindly accept one side without understanding the context. Always consider both changes — the answer is often a combination, not a choice. If you're unsure, talk to the developer who made the conflicting change. A five-minute conversation saves an hour of debugging later.

To reduce conflicts in the first place, keep branches short, communicate with your team about what files you're working on, and rebase frequently to stay close to main. Conflict resolution is a skill, like debugging — the more you do it deliberately, the faster you get.

conflict_resolution.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# ============================================================
# Simulating and resolvin a merge conflict
# ============================================================

# --- Start by attempting the merge ---
git merge feature/new-header
# Git outputs: Auto-merging index.html
# CONFLICT (content): Merge conflict in index.html
# Automatic merge failed; fix conflicts and then commit the result.

# --- View the conflicted file ---
cat index.html
# <<<<<<< HEAD
# <title>My App - Home</title>
# =======
# <title>Ecommerce Platform - Dashboard</title>
# >>>>>>> feature/new-header
# ... rest of file

# --- Resolve manually: edit the file to keep the new title ---
# Final result: <title>Ecommerce Platform - Dashboard</title>

# --- Stage the resolved file ---
git add index.html

# --- Complete the merge ---
git commit -m "Merge feature/new-header: update page title"

# --- Alternative: use git mergetool to open visual diff ---
# git mergetool
# This launches your configured diff tool (e.g., vimdiff, meld, kdiff3)

# --- If you want to abort the merge entirely ---
git merge --abort
# This restores your branch to the state before the merge attempt
Output
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
<index.html conflict markers shown>
[main 5b9e1a2] Merge feature/new-header: update page title
Pro Tip: Use `git merge --abort` if you're stuck
If a merge conflict gets too messy or you realise you merged the wrong branch, run git merge --abort to go back to the state before the merge attempt. No harm done. You can then re-plan your approach – maybe rebase first to reduce conflicts.
Production Insight
Merge conflicts are not failures – they're a sign that two people improved the same code.
The real mistake is resolving blindly without understanding both changes.
Rule: when in doubt, call the other developer. A 5-minute chat beats an hour of guesswork.
Key Takeaway
Resolve conflicts by combining logic, not choosing sides.
Communicate with teammates to understand intent.
Keep branches short to minimise conflict frequency.
● Production incidentPOST-MORTEMseverity: high

Force Push to Main Wiped Out Teammate's Commits

Symptom
Teammates pull main and find their commits missing; automated CI starts failing because the code they changed is gone.
Assumption
'I was pushing my own branch – force push shouldn't affect others.' But git push --force origin main overwrites the remote main branch regardless of who committed to it.
Root cause
Using bare --force instead of --force-with-lease, which would have aborted if the remote had unexpected commits. Also, branch protection on main was not enabled.
Fix
Immediately notify the team and stop further pushes. Use git reflog on one of the affected developer's local repositories to find the lost commits. Cherry-pick those commits back onto main. Re-enable branch protection to require pull requests and prevent force pushes.
Key lesson
  • Never use bare --force on shared branches – always use --force-with-lease.
  • Enable branch protection on main to block force pushes and require PRs.
  • Educate team on the difference between --force and --force-with-lease.
  • Keep local clones of teammates as recovery points.
Production debug guideSymptom → Immediate Action to Recover4 entries
Symptom · 01
You accidentally committed to the wrong branch.
Fix
Use git log to find the commit hash, then git cherry-pick <hash> onto the correct branch and git reset HEAD~1 on the wrong branch.
Symptom · 02
Merge conflict that you can't resolve.
Fix
Run git mergetool to open a visual diff tool, or manually edit files to resolve markers, then git add and git commit.
Symptom · 03
Lost commits after a bad reset.
Fix
Run git reflog to find the commit hash before the reset, then git reset --hard <hash>.
Symptom · 04
Force push overwrote a teammate's work.
Fix
Use git reflog on the remote (if accessible) or ask the teammate to git push --force-with-lease with their commits. Otherwise, recover from local clones.
★ Git Emergency Cheat SheetQuick commands to fix common Git disasters in under 30 seconds.
Need to undo the last commit but keep changes
Immediate action
Run `git reset --soft HEAD~1`
Commands
git reset --soft HEAD~1
git status to verify changes are unstaged
Fix now
Then reset with git reset HEAD <file> to unstage if needed.
Need to uncommit and discard changes+
Immediate action
Run `git reset --hard HEAD~1` (careful – loses changes)
Commands
git reset --hard HEAD~1
To recover lost changes, use `git reflog` to find the old commit and `git cherry-pick`
Fix now
Only use --hard if you are sure you don't need the changes.
Commit message was wrong+
Immediate action
Run `git commit --amend -m "New message"`
Commands
git commit --amend -m "Fixed login validation"
If already pushed, you need `git push --force-with-lease`
Fix now
Amend only if not shared; if pushed, coordinate with team before force-push.
Accidentally committed sensitive data (password, API key)+
Immediate action
Immediately rotate the credential, then use `git filter-repo` to scrub from history.
Commands
git filter-repo --path .env --invert-paths
Force push all branches: `git push origin --force --all`
Fix now
But assume the secret is compromised; rotate it first, then clean history.
Aspectgit mergegit rebase
History shapeNon-linear — shows branches diverging and joiningLinear — looks like one straight line of commits
Creates extra commitsYes — adds a merge commit to join branchesNo — replays your commits directly on top of the target
Safe on shared branchesYes — does not rewrite existing commitsNo — rewrites commit hashes, breaks others' local copies
Conflict resolutionResolve once in the merge commitResolve once per replayed commit (can be more work)
Readability of git logCan become complex with many branchesClean and easy to follow chronologically
Best used whenMerging a completed PR into mainUpdating your private feature branch before opening a PR
Force push needed afterNoYes — use --force-with-lease, never bare --force

Key takeaways

1
One commit = one logical change. If you can't summarise your commit in one imperative-mood sentence under 50 characters, it's probably two commits.
2
Never commit directly to main. Always branch, always get a review
even if you're the only developer. Your future self is your reviewer.
3
Rebase rewrites history
so only rebase branches that exist only on your own machine. The moment a branch is shared or pushed to origin and reviewed by others, use merge.
4
Secrets committed to Git are compromised immediately
not when someone finds them, but the moment they're pushed. Rotate first, clean history second, always.

Common mistakes to avoid

3 patterns
×

Committing directly to main

Symptom
One broken commit takes down the entire team's workflow, or a feature half-finished gets deployed accidentally.
Fix
Enable branch protection rules in your Git hosting (e.g., GitHub: Settings > Branches > Add rule > check 'Require a pull request before merging'). This physically prevents anyone pushing to main without a review.
×

Writing vague commit messages like 'fix bug' or 'changes'

Symptom
Three months later, git log tells you nothing useful; git blame points to uninformative commits. You have to open every commit to find the one that broke something.
Fix
Adopt the imperative-mood rule: every message completes the sentence 'If applied, this commit will ___'. Example: 'Fix login validator rejecting emails with plus signs' is clear.
×

Committing node_modules, .env, or build artifacts

Symptom
Bloated repository that takes minutes to clone; worse, potential API keys or passwords visible in history forever.
Fix
Create a comprehensive .gitignore before your very first commit. Use gitignore.io to generate one for your specific language and framework.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Walk me through your typical Git workflow when starting a new feature — ...
Q02SENIOR
What is the difference between git merge and git rebase, and when would ...
Q03SENIOR
If a teammate accidentally committed AWS credentials to a public GitHub ...
Q01 of 03SENIOR

Walk me through your typical Git workflow when starting a new feature — from the moment you get the ticket to the moment the code is in production.

ANSWER
I start by creating a feature branch from an up-to-date main. I commit in small logical chunks using imperative messages. I push the branch early to share progress. Once the feature is complete, I rebase on main to keep a linear history (if it's my own branch), then open a pull request. After review and CI passes, the PR is merged (usually via squash merge), and I delete the remote branch. Then I pull updated main to my local. If the feature is large, I break it into multiple smaller PRs to avoid long-lived branches.
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
How often should I commit my code?
02
What is the difference between git pull and git fetch?
03
Should I use Git GUI tools or learn the command line?
🔥

That's Software Engineering. Mark it forged?

7 min read · try the examples if you haven't

Previous
Software Testing Types
9 / 16 · Software Engineering
Next
Documentation Best Practices