📂 Real-World Examples
Each example is a scenario-based walkthrough using only Git commands — no framework-specific steps. Click any card to expand it.
Scenario
You're starting a brand new project from scratch and need to get it into GitLab.
Step 1 — Create the GitLab Repository First
- Go to your GitLab instance → New Project → Create blank project
- Set a name (e.g.,
MyApp) - Set visibility (Private/Internal/Public)
- Uncheck "Initialize repository with a README" — you'll push from local
- Copy the repo URL (e.g.,
git@gitlab.company.com:team/MyApp.git)
Step 2 — Create Your Project Folder
Create the project folder and add your initial files however your stack requires. Once you have something worth tracking, continue below.
Step 3 — Initialize Git and Add .gitignore
git init
Create a .gitignore at the root to stop build output and IDE files from being committed:
# Build output (adjust for your stack)
bin/
obj/
dist/
out/
# IDE / Editor
.vs/
.idea/
*.user
*.suo
.vscode/
# OS
.DS_Store
Thumbs.db
# Environment / secrets
.env
.env.local
*.local
Step 4 — First Commit
git add .
git status # review what's staged — confirm no build output appears
git commit -m "Initial commit: scaffold MyApp"
Step 5 — Connect to GitLab and Push
git remote add origin git@gitlab.company.com:team/MyApp.git
# Rename branch to main if needed
git branch -M main
git push -u origin main
After this, every future push on this branch is just git push.
git push -u origin main sets origin/main as the upstream for your local branch. After that, Git knows where to push/pull without specifying it each time.Scenario
The repo already exists on GitLab. You need to get it on your machine and start contributing.
Step 1 — Clone the Repository
git clone git@gitlab.company.com:team/MyApp.git
# Or clone into a specific folder name
git clone git@gitlab.company.com:team/MyApp.git my-local-folder
cd MyApp
This creates a full local copy including the entire history. The remote is automatically named origin.
Step 2 — Understand What You Cloned
git log --oneline --graph -20 # recent history
git branch -a # all branches (local + remote)
git remote -v # confirm origin points to GitLab
Step 3 — Always Pull Before You Start Work
git switch main
git pull
Step 4 — Make Changes and Push
# Create a branch for your work
git switch -c feature/add-user-endpoint
# ... make your changes ...
git status
git diff
git add src/users/users.controller.js
git commit -m "Add GET /users endpoint"
git push -u origin feature/add-user-endpoint
Then go to GitLab and open a Merge Request from your branch into main.
Step 5 — Stay in Sync While Working
git fetch origin
git rebase origin/main
If You Accidentally Work on Main
git switch -c feature/my-work # create branch at current position
git switch main
git reset --hard origin/main # reset main back to remote state
git switch feature/my-work # your changes are safe on the new branch
Scenario
A team shares one GitLab repository. Everyone needs to work on their own features without breaking each other's work.
The Golden Rules
- Never commit directly to
main - Always pull before starting work
- One feature = one branch = one Merge Request
- Small, frequent commits — easier to review and revert
GitHub Flow — Daily Workflow
# 1. Start from latest main
git switch main
git pull
# 2. Create your feature branch
git switch -c feature/user-auth
# 3. Work, commit often
git add -p
git commit -m "Add JWT authentication middleware"
# 4. Keep in sync with main daily
git fetch origin
git rebase origin/main
# 5. Push and open a Merge Request
git push -u origin feature/user-auth
After MR is approved and merged:
git switch main
git pull
git branch -d feature/user-auth # clean up local branch
GitFlow Branch Naming
| Branch type | Format | Example |
|---|---|---|
| Feature | feature/short-description | feature/user-auth |
| Release | release/version | release/1.2.0 |
| Hotfix | hotfix/short-description | hotfix/login-crash |
Common Team Scenarios
"I committed to the wrong branch"
git log --oneline -5
git switch feature/correct-branch
git cherry-pick <commit-hash>
git switch wrong-branch
git reset --hard HEAD~1 # only if not pushed yet
"My branch is 10 commits behind main"
git fetch origin
git rebase origin/main
git push -f origin feature/my-branch
Scenario
Two people edited the same lines in the same file. Git can't auto-merge — it needs you to decide what the final result should be.
Reading Conflict Markers
<<<<<<< HEAD
function getUser(id) {
return db.users.find(id);
}
=======
async function getUser(id) {
return await db.users.findById(id);
}
>>>>>>> feature/async-refactor
| Marker | Meaning |
|---|---|
<<<<<<< HEAD | Start of your version |
======= | Divider between the two versions |
>>>>>>> branch-name | End of the incoming version |
Delete the markers and keep the correct final code — often a combination of both sides.
Resolving a Merge Conflict
git status # shows which files have conflicts
# Edit each conflicted file
git add src/users/service.js # mark as resolved
git merge --continue # or: git commit
To abort and go back: git merge --abort
Resolving a Rebase Conflict
git fetch origin
git rebase origin/main
# Git pauses on each conflicted commit
# Edit the file, then:
git add src/users/service.js
git rebase --continue
After the rebase completes, force-push your branch:
git push -f origin feature/my-branch
main or develop.Conflict in a Lock File (package-lock.json, yarn.lock, etc.)
# Don't manually merge lock files — accept one side then regenerate
git checkout --ours package-lock.json
# or
git checkout --theirs package-lock.json
# Regenerate
npm install
git add package-lock.json
git rebase --continue
Prevention
- Rebase onto
mainfrequently — the longer you diverge, the bigger the conflict - Communicate — if two people are touching the same area, coordinate
- Keep PRs small — large PRs have more surface area for conflicts
Quick Decision Guide
| What happened | Not pushed yet | Already pushed |
|---|---|---|
| Wrong commit message | git commit --amend | Avoid — others may have pulled |
| Committed wrong files | git reset HEAD~1 | git revert <hash> |
| Committed to wrong branch | cherry-pick + reset | git revert + new MR |
| Discard all local changes | git restore . | — |
| Accidentally deleted a file | git restore <file> | — |
Fix the Last Commit Message (Not Pushed)
git commit --amend -m "Correct message here"
Undo the Last Commit — Keep Changes Staged
git reset --soft HEAD~1
Undo the Last Commit — Keep Changes Unstaged
git reset HEAD~1
Undo the Last Commit — Discard Everything
git reset --hard HEAD~1
--hard permanently destroys your changes. Only use it when you're certain.Revert a Commit That's Already Pushed
git log --oneline -10
git revert a1b2c3d # creates a new "Revert..." commit
git push
Discard Uncommitted Changes
git restore src/users/service.js # one file
git restore . # everything
Recover a "Lost" Commit with Reflog
git reflog
# Find the hash of the lost commit
git cherry-pick f6e5d4c # bring it back
Remove a File from Git Without Deleting It Locally
git rm --cached .env
echo ".env" >> .gitignore
git commit -m "Stop tracking .env"
Scenario
You're mid-feature when something urgent comes up. You're not ready to commit, but you can't leave the working directory dirty.
Basic Stash
git stash # save tracked changes
git stash -u # also include untracked (new) files
Give Your Stash a Name
git stash push -m "WIP: token validation logic, halfway done"
Full Workflow
# Mid-feature, urgent task arrives
git stash push -m "WIP: token validation"
git switch main
git pull
git switch -c hotfix/login-crash
# ... fix, commit, push ...
# Return to your feature
git switch feature/user-auth
git stash pop
List, Apply, Drop
git stash list # see all stashes
git stash pop # apply most recent AND remove from list
git stash apply # apply most recent, keep in list
git stash apply stash@{1} # apply a specific stash by index
git stash drop stash@{1} # delete a specific stash
git stash clear # delete ALL stashes
Inspect a Stash Before Applying
git stash show stash@{0} # summary
git stash show -p stash@{0} # full diff
WIP Commit as an Alternative
# Create a temporary commit
git add -A
git commit -m "WIP: do not merge"
# When you come back, undo it
git reset HEAD~1 # keeps your changes unstaged
git log — useful for long-running work-in-progress.Scenario
You're working on feature/user-auth with uncommitted changes. A critical bug is reported in production — it needs to be fixed and deployed immediately.
Step 1 — Stash Your In-Progress Work
Don't commit half-done work. Stash it cleanly so you can come back to it later.
git stash push -m "WIP: user auth - mid refactor"
git status # confirm working directory is clean
Step 2 — Switch to Main and Pull the Latest
git switch main
git pull # make sure you have the latest production code
Step 3 — Create a Hotfix Branch
Always branch off main for a hotfix — not off your feature branch.
git switch -c hotfix/fix-login-crash
Step 4 — Fix, Commit, Push
# Make the fix
git add path/to/fixed-file.cs
git commit -m "fix: prevent null ref crash on login page"
git push -u origin hotfix/fix-login-crash
Open a Pull Request / Merge Request targeting main. Get it reviewed and merged.
Step 5 — Return to Your Feature and Rebase
Once the hotfix is merged, update your feature branch so it's built on top of the fix, not the old code.
git switch feature/user-auth
git stash pop # restore your WIP changes
git rebase origin/main # replay your feature commits on top of the hotfix
stash pop causes conflicts, resolve them the same way as a merge conflict — edit the files, then run git add <file> and git stash drop to finish.main to fix production. A hotfix branch from main is always cleaner and safer.Scenario
You've been working on a feature for a day and your branch has commits like: "WIP", "fix typo", "actually fix it", "add tests", "test fix". Before raising a PR you want these squashed into one clean commit.
Step 1 — Check How Many Commits to Squash
git log --oneline origin/main..HEAD # shows commits on your branch not yet in main
Say you see 5 commits. You'll squash all 5 into 1.
Step 2 — Start Interactive Rebase
git rebase -i HEAD~5 # replace 5 with your actual commit count
Your editor opens with a list like:
pick a1b2c3d feat: add login page skeleton
pick b2c3d4e WIP
pick c3d4e5f fix typo
pick d4e5f6a actually fix it
pick e5f6a7b add tests
Step 3 — Mark Commits to Squash
Keep the first commit as pick. Change the rest to s (squash) — they'll be merged into the first:
pick a1b2c3d feat: add login page skeleton
s b2c3d4e WIP
s c3d4e5f fix typo
s d4e5f6a actually fix it
s e5f6a7b add tests
Save and close the editor. Git opens a second editor to write the final combined commit message — replace everything with one clean message:
feat: add login page with form validation and unit tests
Step 4 — Force Push Your Branch
You've rewritten history on your local branch — you need to force push. This is safe on your own feature branch that no one else is working on.
git push --force-with-lease
--force-with-lease is safer than --force — it refuses to push if someone else has pushed to the branch since you last fetched, preventing you from accidentally overwriting their commits.main or any shared branch — it rewrites history and causes problems for everyone.