🌳 Deep Git Workflows: Let's Learn from ZERO!
Git is not just git add, git commit, and git push. 😄 When multiple developers work on the same project, you need to know:
- 🔀 How to combine code from different branches
- 🧹 How to keep a clean commit history
- ⚔️ How to fix conflicts when two developers change the same code
- ⏪ How to undo mistakes safely
- 🏷️ How to manage versions and releases
- 🏢 How professional teams organize their branches
We'll learn everything with simple real-world examples, commands, and practical scenarios. Let's go! 🚀
1. 🧱 First, Git Basics
Imagine you're building an Instagram-like Social Feed app:
SocialFeed/ │ ├── frontend/ │ ├── App.tsx │ └── Feed.tsx │ ├── backend/ │ ├── main.py │ └── auth.py │ └── README.md Git helps you track changes made to these files. Some important terms:
| Term | Simple meaning |
|---|---|
| 📁 Repository (repo) | Your project tracked by Git |
| 📸 Commit | A saved snapshot of your changes |
| 🌿 Branch | An independent line of development |
| 🔀 Merge | Combining changes from branches |
| 🔁 Rebase | Replaying commits on top of another branch |
| ⚔️ Conflict | Git cannot automatically combine some changes |
| 👉 HEAD | Pointer to your current commit |
| ☁️ Remote | A copy of your repo hosted elsewhere, like GitHub |
🌿 What is a branch?
Your main app works perfectly, and you want to build a login feature without disturbing it. Instead of changing main directly, you create a separate branch:
git branch feature/login # create the branch git switch feature/login # move to it Now you can work independently, and your main branch stays unaffected until you integrate your changes.
📌 Important: Branches are lightweight pointers to commits. They don't usually create a complete separate copy of your project.
2. 🔀 Git Merge vs Git Rebase
🎯 Very important for interviews and real projects!
Both bring changes from one branch into another, but they work differently.
🎬 Scenario
Your team has a main branch. You create feature/login. Both initially point to commit C. Then:
- You make commit D in
feature/login - Another developer makes commit E in
main
D (feature/login) / A -- B -- C -- E (main) You want the latest main changes in your feature branch. Two approaches:
🅰️ Git Merge
Merge combines two branches while preserving their existing history.
git switch feature/login git merge main What happens:
- Git checks the changes in your feature branch
- Git checks the changes in
main - Git combines them
- It creates a merge commit if a normal merge requires one
D ---- M / / A -- B -- C -- E - D = your login feature commit
- E = another developer's
maincommit - M = the merge commit connecting both histories
📚 Analogy: Two people write separate book chapters. You combine both chapters and preserve who wrote what and when.
✅ Advantages
- Preserves complete development history
- Doesn't rewrite existing commits
- Safe for branches shared with other developers
- Shows when two lines of development were combined
❌ Disadvantages
- Can create many merge commits
- History can get complicated when many branches are merged often
🅱️ Git Rebase
Rebase takes your commits and replays them on top of another branch's latest commit.
git switch feature/login git rebase main What happens:
- Git identifies the commits unique to your feature branch
- It temporarily sets those commits aside
- It moves your feature branch to the latest
maincommit - It replays your feature commits on top
Before rebase:
D (feature/login) / A -- B -- C -- E (main) After rebase:
A -- B -- C -- E -- D' (feature/login) Notice D becomes D'. Git creates a new commit with the same changes but a different commit hash.
📚 Analogy: You wrote your chapter based on an old draft. Your colleague updates the main book. Instead of attaching your old chapter on the side, you reapply it on top of the updated book.
✅ Advantages
- Cleaner, linear history
- Avoids unnecessary merge commits
- History is easier to read
- Great before merging feature branches
❌ Disadvantages
- Rewrites commit history
- Can cause problems if others already use those commits
- Conflicts may need resolving while replaying commits
⚖️ Merge vs Rebase: Comparison
| Feature | Merge | Rebase |
|---|---|---|
| History | Preserves history | Rewrites commit history |
| Merge commit | Often created | Not created by rebase itself |
| Commit hashes | Existing commits remain | Replayed commits get new hashes |
| Shape | Can be non-linear | Usually linear |
| Shared branches | Generally safe | Risky if commits are shared |
| Main purpose | Combine histories | Reapply commits on a new base |
🧠 Remember: Merge = preserve history. Rebase = cleaner, rewritten history.
Use merge when:
- You're combining changes from shared branches
- You want to preserve the complete history
- Your team prefers explicit merge records
Use rebase when:
- You're updating your own feature branch with the latest
main - You want a clean commit history
- Your commits haven't been shared with others
🚨 Golden rule: Never casually rebase commits that other developers have already based their work on.
3. 🧹 Interactive Rebase
Suppose you made these commits while building login:
1. Created login component 2. Fixed login button 3. Fixed login button again 4. Added validation 5. Small CSS correction Showing this to your mentor? Messy! 😬 You may want to combine, reorder, edit, or remove commits. That's interactive rebase: it lets you edit your commit history.
git rebase -i HEAD~5 -
git rebase→ rebase operation -
-i→ interactive mode -
HEAD~5→ the last 5 commits (including HEAD)
Git opens an editor with something like:
pick a123 Created login component pick b234 Fixed login button pick c345 Fixed login button again pick d456 Added validation pick e567 Small CSS correction You edit the instructions. Here are the important commands:
🅰️ pick: keep the commit as is
pick a123 Created login component 🅱️ squash: combine with the previous commit (keep both messages)
pick a123 Created login component pick b234 Fixed login button squash c345 Fixed login button again Result could be: Created login component and fixed login button. Use it to turn many small commits into one meaningful commit.
🅲 fixup: combine, but discard the later message
pick a123 Created login component fixup b234 Fixed login button -
squash= combine changes and messages -
fixup= combine changes, discard the later message
🅳 reword: change only the commit message
reword a123 login changes Old: login changes → New: Implemented user login API. The code stays the same.
🅴 edit: pause the rebase at that commit
edit a123 Created login component Then modify the code or amend:
git add . git commit --amend git rebase --continue 🅵 drop: remove the commit
drop b234 Fixed login button ⚠️ Dropping a commit also removes its changes from the resulting history unless they exist elsewhere.
🅶 Reorder commits
Just change the order of the lines:
# Before pick a123 Created login component pick b234 Added validation pick c345 Added styling # After pick c345 Added styling pick a123 Created login component pick b234 Added validation Git tries to replay them in the new order. Reordering can cause conflicts if later commits depend on earlier ones.
📋 Interactive rebase summary
| Command | Meaning |
|---|---|
pick | Keep commit |
reword | Change commit message |
edit | Pause and modify commit |
squash | Combine commits and messages |
fixup | Combine commits, discard later message |
drop | Remove commit |
💡 Practical example: squash five small auth commits into one clean commit: Implemented authentication with login validation.
4. ⚔️ Resolving Git Merge Conflicts
🎯 Extremely important in real projects!
A merge conflict happens when Git cannot automatically decide how to combine changes from different branches. Git is good at combining changes, but sometimes it needs human help.
🎬 Real-life example
main has this in auth.py:
def login(): return "Login successful" Another developer changes that line in main:
def login(): return "Welcome back!" You change the same line in your feature branch:
def login(): return "User authenticated successfully" Now you run:
git merge main Both branches changed the same part of the same file. Git doesn't know your intention, so it reports a conflict. 💥
👀 How does a conflict look?
Git edits your file like this:
def login(): <<<<<<< HEAD return "User authenticated successfully" ======= return "Welcome back!" >>>>>>> main | Marker | Meaning |
|---|---|
<<<<<<< HEAD | Start of your current branch's version |
======= | Separates the two versions |
>>>>>>> main | End of the incoming branch's version |
- Above
=======→ your current branch's change - Below
=======→ the incoming branch's change
🛠️ How to resolve it
Say you want to keep your version. Manually delete the three marker lines and keep the final correct code:
def login(): return "User authenticated successfully" Then:
git add auth.py # tells Git you resolved this file git commit # completes the merge commit 🔁 Resolving a conflict during a rebase
# 1. Open the conflicted file and fix the code # 2. Mark it as resolved git add auth.py # 3. Continue the rebase git rebase --continue Want to cancel?
git rebase --abort # cancel a rebase git merge --abort # cancel a merge ⚠️ HEAD and incoming changes: during a normal merge,
HEADis your current branch and the incoming version comes from the branch you're merging. During a rebase, Git is replaying commits, so the terminology can be confusing. Don't blindly assumeoursandtheirsmean what you expect.
🛡️ How to prevent conflicts
- Frequently pull or rebase the latest changes from
main - Communicate with teammates about which files you're modifying
- Avoid unnecessarily large changes in one branch
- Keep commits small and focused
- Resolve conflicts early, not at the end
💡 Remember: A conflict is not a Git error. It's Git asking you to make a decision.
5. 🗄️ Git Stash
You're working on login and have changed:
frontend/Login.tsx backend/auth.py Suddenly your mentor says: "Please switch to main and fix this production bug right now!" 😱 But your changes are unfinished, and you don't want to commit incomplete code.
📌 Git stash temporarily stores your uncommitted changes so you get a clean working directory. Think of it as putting unfinished work into a temporary drawer 🗄️.
A. Save your changes
git stash git switch main # now you can switch safely B. View your stashes
git stash list stash@{0}: WIP on feature/login stash@{1}: WIP on feature/profile (WIP = Work In Progress)
C. Restore your changes
git stash apply # restore the latest stash, KEEP it in the list git stash pop # restore the latest stash, REMOVE it from the list D. Stash with a message
git stash push -m "Login feature work" E. Delete stashes
git stash drop stash@{0} # delete one stash git stash clear # delete ALL stashes (careful!) F. Stash untracked files too
By default, git stash does not include untracked files (like a brand-new backend/new_auth_service.py). Use:
git stash -u 📋 Stash summary
| Command | Purpose |
|---|---|
git stash | Temporarily save tracked changes |
git stash -u | Also save untracked files |
git stash list | View saved stashes |
git stash apply | Restore but keep the stash |
git stash pop | Restore and remove the stash |
git stash drop | Delete a particular stash |
git stash clear | Delete all stashes |
⚠️ Stash is temporary storage, not a permanent backup. Don't use it as a replacement for meaningful commits.
6. 🍒 Git Cherry-Pick
You're working on the profile feature. Your teammate works on login and makes three commits:
Commit A: Created login page Commit B: Fixed login validation Commit C: Updated login styling You only need the validation fix (B). You don't want to merge the whole login branch because it has other unfinished changes.
📌 Cherry-pick takes a specific commit from another branch and applies its changes to your current branch. Like picking one item from a basket instead of taking the whole basket. 🧺
Step 1: find the commit ID
git log --oneline c345 Updated login styling b234 Fixed login validation a123 Created login page Step 2: switch to your branch
git switch feature/profile Step 3: cherry-pick
git cherry-pick b234 Git applies the changes from b234 to your current branch, creating a new commit with the same changes but a different hash.
Conflict? Resolve it manually, then:
git add . git cherry-pick --continue # or cancel: git cherry-pick --abort 🆚 Merge vs Cherry-pick
| Merge | Cherry-pick |
|---|---|
| Combines branch histories | Applies selected commit changes |
| Usually brings multiple commits | Can bring a specific commit |
| Useful for integrating branches | Useful for reusing individual fixes |
💡 Real-world use: a teammate fixes a critical auth bug in one branch. You cherry-pick just that fix into a release branch without merging their unfinished work.
7. ⏪ Git Revert vs Git Reset
🎯 Must know before working on shared repositories!
You accidentally committed bad code and want to undo it. Git offers two major approaches, and they work very differently.
🅰️ Git Revert
Revert creates a new commit that reverses the changes of an earlier commit. It does not delete the original commit.
Before: A -- B -- C -- D (D introduced a bug) git revert D After: A -- B -- C -- D -- E (E undoes D) - D = the original incorrect change
- E = the new commit that undoes D
The original history is preserved. This is especially useful on shared branches like main.
git revert <commit-id> 🅱️ Git Reset
Reset moves your current branch pointer (HEAD) to an earlier commit. Depending on the mode, it also changes your staging area and working directory.
First, the three Git areas:
| Area | Simple meaning |
|---|---|
| 📝 Working directory | Where you edit your actual files |
| 🎭 Staging area | Where you prepare changes using git add |
| 📦 Repository | Where your commits are stored |
1️⃣ Soft reset
git reset --soft HEAD~1 - Removes the latest commit from branch history
- Keeps changes staged
- Your code is preserved
Use when: you want to combine the last commit with other changes into a new commit.
2️⃣ Mixed reset (the default)
git reset --mixed HEAD~1 - Removes the latest commit
- Unstages the changes
- Keeps your code in the working directory
Use when: you want to modify some files before committing again.
3️⃣ Hard reset 🚨
git reset --hard HEAD~1 - Moves HEAD back one commit
- Resets the staging area
- Resets tracked files in your working directory to match the target commit
🚨 Destructive! You can permanently lose uncommitted work, and recovering discarded commits is not guaranteed.
📊 Soft vs Mixed vs Hard
| Reset type | Commit | Staging area | Working directory |
|---|---|---|---|
| Soft | Removed from branch | Changes kept | Changes kept |
| Mixed | Removed from branch | Changes unstaged | Changes kept |
| Hard | Removed from branch | Reset | Reset |
🧠 Easy memory trick:
- Soft: keep everything staged
- Mixed: keep the code, unstage it
- Hard: discard tracked changes to match the target commit
⚔️ Revert vs Reset: the biggest difference
| Feature | Revert | Reset |
|---|---|---|
| How it works | Creates a reversing commit | Moves the branch pointer |
| Original commit | Preserved | Removed from current branch history |
| History | Preserved | Rewritten |
Shared main branch | Usually safe ✅ | Risky ⚠️ |
| Best for | Undoing shared changes | Reorganizing local history |
Which one should you use?
- Pushed a faulty commit to the team's
main? →git revert <commit-id>, because others may already have the original commit. - Made an accidental commit locally and haven't pushed? →
git reset --soft HEAD~1(or another mode).
🏆 Golden rule: shared/published history → prefer
revert. Your own unpublished local history →resetcan be useful.📝
HEAD~1means the first parent of the current commit. Merge commits have multiple parents, so reverting a merge commit needs extra care, usuallygit revert -m.
8. 🏷️ Git Tags: Tagging Releases
Your app evolves through milestones:
- Version 1.0: basic feed
- Version 1.1: login and authentication
- Version 1.2: comments and likes
- Version 2.0: major redesign
How do you mark these in Git? With tags.
📌 A Git tag is a label attached to a specific commit. It marks important points in history, especially software releases.
Commit A -- Commit B -- Commit C -- Commit D ↑ ↑ v1.0 v2.0 A. Lightweight tag
git tag v1.0 B. Annotated tag (preferred for releases)
git tag -a v1.0 -m "First stable release" It stores extra info: tag name, tagger's information, date, and message.
C. View tags
git tag D. Tag an older commit
git tag -a v1.0 <commit-id> -m "First stable release" E. Push tags to GitHub
Creating a tag locally doesn't send it to GitHub automatically.
git push origin v1.0 # push one tag git push origin --tags # push all tags F. Delete a tag
git tag -d v1.0 # delete locally git push origin --delete v1.0 # delete remotely 🔢 Semantic Versioning (SemVer)
Versions like 1.2.3 follow MAJOR.MINOR.PATCH:
| Part | Meaning |
|---|---|
1 MAJOR | Breaking changes 💥 |
2 MINOR | New compatible features ✨ |
3 PATCH | Bug fixes 🩹 |
| Version | Meaning |
|---|---|
1.0.0 | First stable release |
1.1.0 | Added a new feature |
1.1.1 | Fixed a bug |
2.0.0 | Introduced breaking changes |
🎯 Interview point: tags are labels for specific commits, whereas branches are movable pointers that normally advance as new commits are added.
9. 🏢 Git Branching Strategies
You work in a company with 10 developers: login, profile, notifications, feed, comments. If everyone changes main directly, chaos! 😵 Teams use branching strategies. Three popular ones:
- 🏛️ Git Flow
- 🐙 GitHub Flow
- 🚄 Trunk-Based Development
🏛️ A. Git Flow (structured)
A strategy with multiple long-lived and short-lived branches. Useful for projects with scheduled releases and separate development, testing, and production workflows. It has five branch types:
| Branch | Purpose |
|---|---|
🟢 main | Production-ready code. Only stable, released versions |
🔵 develop | Integration branch for upcoming features |
🌿 feature/* | One per feature (e.g. feature/login), created from develop and merged back into develop |
📦 release/* | Release preparation (e.g. release/1.0): final testing, bug fixes, version updates. Merged into main and usually back into develop |
🚑 hotfix/* | Critical production fixes (e.g. hotfix/login-security), created from main and merged into main and develop |
main ●─────────────────────●─────────────●──────▶ \ ↗ (release) ↑ \ / │ hotfix develop ●──●──────●─────●──────────●──────●──────▶ \ ↗ ●──● feature/login (The diagram is conceptual. Real Git Flow merges create more detailed history.)
✅ Advantages
- Clear separation between development and production
- Suitable for planned releases
- Supports hotfixes and release stabilization
❌ Disadvantages
- More branches to manage
- More complicated workflow
- Can slow down teams that release frequently
🐙 B. GitHub Flow (simple and widely used)
Uses just:
main- Short-lived feature branches
- Pull Requests (PRs)
How it works:
# Step 1: start from main git switch main git pull origin main # Step 2: create a feature branch git switch -c feature/login # Step 3: make changes and commit git add . git commit -m "Add login functionality" # Step 4: push your branch git push -u origin feature/login Step 5: create a Pull Request on GitHub. Teammates can review code, add comments, suggest improvements, request changes, or approve.
Step 6: merge the PR once it's approved and checks pass.
Step 7: delete the feature branch. 🧹
✅ Advantages
- Simple workflow
- Easy collaboration
- Supports CI/CD
- Less branch management
❌ Disadvantages
- Requires good automated testing and code review
-
mainmust stay stable - Teams need to coordinate deployments and releases
🚄 C. Trunk-Based Development
The trunk is the main development line, usually main. Developers integrate small changes into main frequently instead of keeping long-lived feature branches. They may use very short-lived branches, or even commit directly to the trunk when the team's workflow permits.
git switch main git pull origin main git switch -c feature/login # Make small changes git add . git commit -m "Add login validation" git push -u origin feature/login The branch is reviewed and integrated quickly.
🚩 What are Feature Flags?
Your notification feature isn't ready for users, but you want its code in main. Keep it disabled with a flag:
ENABLE_NOTIFICATIONS = False if ENABLE_NOTIFICATIONS: show_notifications() When it's ready, you enable it. Real applications generally use proper configuration or feature-flag services instead of hardcoding flags.
✅ Advantages
- Frequent integration
- Fewer large, complicated merges
- Faster feedback
- Supports continuous delivery
- Avoids long-lived branch problems
❌ Disadvantages
- Requires strong automated testing
- Developers must integrate small changes frequently
- Incomplete features need careful isolation
- Requires good CI/CD practices
📊 Compare all three
| Feature | Git Flow 🏛️ | GitHub Flow 🐙 | Trunk-Based 🚄 |
|---|---|---|---|
main branch | Yes | Yes | Yes |
develop branch | Yes | Usually no | No |
| Feature branches | Yes | Yes | Short-lived or none |
| Complexity | High | Low | Low to moderate |
| Integration | Around feature/release milestones | Through PRs | Very frequent |
| Best for | Scheduled releases | Web apps and teams using PRs | Continuous delivery |
- Git Flow: more structured, multiple branches, scheduled releases
- GitHub Flow:
main+ feature branches + Pull Requests - Trunk-Based: integrate small changes into
mainfrequently
💡 For a typical small team building a web app, GitHub Flow is a straightforward starting point. Trunk-Based Development shines when you have strong automated testing and deployment practices.
10. 📖 Clean, Readable Git History as Documentation
One of the most underrated Git concepts! Imagine joining a company and opening its commit history:
1. changes 2. final changes 3. fixed 4. updated 5. test 6. again fixed 7. new changes Can you tell what was built? Probably not. 😵 Now compare:
1. Add user authentication API 2. Implement password hashing using Argon2 3. Add session expiration handling 4. Fix login validation for invalid credentials 5. Add cursor-based pagination to home feed 6. Implement post like and unlike endpoints Much better! ✨ That's why clean history matters.
✍️ What makes a good commit message?
git commit -m "Add user authentication API" A good message is short, meaningful, specific, and describes the change.
| ❌ Bad | ✅ Good |
|---|---|
changes | Add user profile API |
fixed | Fix login validation error |
updated code | Add pagination to home feed |
final | Implement password hashing |
test | Add unit tests for auth service |
⚛️ What is atomic committing?
Atomic = small and focused. One commit should ideally represent one logical change. Don't mash login API, profile API, a migration, UI styling, and a notification service into one giant commit. Instead:
Commit 1: Add login API Commit 2: Implement password hashing Commit 3: Add profile update endpoint Commit 4: Add profile validation tests This makes debugging and code review much easier.
🔍 Useful commands for reading history
git log # full history git log --oneline # compact history git log --oneline --graph --all # history as a graph git log --stat # which files changed per commit git log -p # actual changes per commit git show <commit-id> # inspect one commit 🤔 Why is Git history "documentation"?
Someone asks: "Why did we change the authentication logic last month?" You search the history and find Fix session validation for expired access tokens, then inspect the commit to see exactly what changed. History helps answer:
- What was changed?
- When was it changed?
- Why? (if the commit or PR explains the reason)
- Which files were affected?
- Which developer contributed it?
📝 Git history doesn't replace proper project documentation, but it's a valuable record of development decisions.
11. 🧵 A Real Team Workflow, Start to Finish
Your mentor says: "Implement the user profile update API." Here's a professional workflow:
Step 1: get the latest code
git switch main git pull origin main Step 2: create your feature branch
git switch -c feature/profile-update Step 3: develop your feature
backend/ api/ profile.py services/ profile_service.py schemas/ profile_schema.py Step 4: check your changes
git status # modified, staged and untracked files git diff # the actual changes Step 5: commit
git add . git commit -m "Add user profile update API" Step 6: update your branch if main has changed
git fetch origin git rebase origin/main git fetch downloads remote updates without integrating them. git rebase replays your local commits on top of the latest remote main. If there are conflicts, resolve them and run:
git add . git rebase --continue Step 7: push your feature branch
git push -u origin feature/profile-update If you rebased commits that were already pushed to your own feature branch, you may need:
git push --force-with-lease This is safer than --force because it checks that the remote branch hasn't moved unexpectedly. Still, coordinate with teammates before rewriting a shared branch.
Step 8: create a Pull Request
On GitHub: feature/profile-update → main. Your teammates review the changes.
Step 9: merge the Pull Request
After approval and successful checks, merge using your team's chosen merge method.
Step 10: tag the release
git tag -a v1.2.0 -m "Release version 1.2.0" git push origin v1.2.0 🎉 You've followed a complete professional Git workflow!
12. 📋 Quick Revision: Important Git Commands
| Requirement | Command |
|---|---|
| Check current branch | git branch --show-current |
| List branches | git branch |
| Create branch | git branch feature/name |
| Switch branch | git switch branch-name |
| Create and switch | git switch -c feature/name |
| Merge | git merge main |
| Rebase | git rebase main |
| Interactive rebase | git rebase -i HEAD~5 |
| Abort rebase | git rebase --abort |
| Continue rebase | git rebase --continue |
| View conflicts | git status |
| Save changes temporarily | git stash |
| Restore stash | git stash pop |
| View stashes | git stash list |
| Apply a specific commit | git cherry-pick commit-id |
| Undo a commit safely | git revert commit-id |
| Soft reset | git reset --soft HEAD~1 |
| Mixed reset | git reset --mixed HEAD~1 |
| Hard reset | git reset --hard HEAD~1 |
| View history | git log --oneline --graph --all |
| Create release tag | git tag -a v1.0 -m "Release" |
| Push a tag | git push origin v1.0 |
| Download remote updates | git fetch origin |
| Push branch | git push origin branch-name |
13. 🎤 Interview Questions
Q1. What is the difference between Git merge and Git rebase?
Merge combines branches while preserving their existing history, often creating a merge commit. Rebase replays commits on top of another branch, creating new commit hashes and generally a linear history.
Q2. What is interactive rebase?
It lets us modify commit history. We can squash, reorder, edit, reword or drop commits.
Q3. What is a merge conflict?
It occurs when Git cannot automatically combine changes, usually because different branches modify overlapping parts of a file. We manually resolve the changes and mark the files as resolved.
Q4. What is Git stash?
It temporarily saves uncommitted changes and cleans the working directory so we can switch branches or work on something else.
Q5. What is cherry-pick?
It applies a specific commit from another branch to our current branch without merging the entire branch.
Q6. What is the difference between revert and reset?
Revert creates a new commit that reverses an earlier commit, preserving history. Reset moves the branch pointer to an earlier commit and can remove commits from the current branch history.
Q7. Explain soft, mixed and hard reset.
- Soft: keeps changes staged
- Mixed: keeps changes unstaged
- Hard: discards tracked-file changes to match the target commit
Q8. What are Git tags?
Labels attached to specific commits, commonly used to mark software releases such as v1.0.0.
Q9. What is Git Flow?
A structured branching strategy using mainSource:https://dev.to/deepika_pusala/deep-git-workflows-rebasing-conflict-resolution-branching-strategies-explained-from-zero-15an
