Git & Github- Differant use case scenarios for Devolpers daily work
Published 2026-08-01 · Language: en
This guide will equip you with the practical Git and GitHub workflows essential for daily development tasks, enabling you to confidently manage features, fixes, and collaborative projects from inception to integration. 1. Initiating a New Feature or Fix Development Photo by Compagnons · unsplash When embarking on a new development task, the first step is typically to ensure your local repository i
This guide will equip you with the practical Git and GitHub workflows essential for daily development tasks, enabling you to confidently manage features, fixes, and collaborative projects from inception to integration.
1. Initiating a New Feature or Fix Development
Photo by Compagnons · unsplash
When embarking on a new development task, the first step is typically to ensure your local repository is up-to-date and then create a dedicated branch for your work. This isolation prevents interference with the main codebase and facilitates independent development.
Cloning a Repository:
Before any work can begin, you need a local copy of the remote repository. This is achieved using the git clone command.
Explanation: git clone downloads the entire repository history, including all branches and commits, from a remote source (like GitHub) to your local machine. It also automatically sets up a remote tracking branch named origin/main (or origin/master ) and creates a local main branch pointing to it.
Example: Cloning a repository from GitHub.
git clone https://github.com/your-org/your-repo.git
Ensuring an Up-to-Date Base:
Before creating a new feature branch, it's crucial to ensure your local main (or develop ) branch is synchronized with the remote to avoid working on stale code.
Command: git pull origin main (or develop ) fetches new changes and merges them into your current branch.
Creating a Dedicated Feature Branch:
The core principle of modern Git workflows is to develop new features or fixes on separate branches. This keeps the main branch stable and allows for concurrent development by multiple team members.
Command: git branch feature/my-new-feature creates a new branch named feature/my-new-feature , typically off the current branch (which should be main or develop ).
Naming Conventions: Adopt a consistent naming convention, such as feature/<description> , bugfix/<issue-id> , or hotfix/<description> . This improves readability and organization.
Switching to the New Branch:
After creating the branch, you must switch your working directory to it to start making changes.
Command: git checkout feature/my-new-feature switches to the specified branch.
💡 Pro-Tip: You can combine branch creation and switching into a single command: git checkout -b feature/my-new-feature . This is equivalent to git branch feature/my-new-feature followed by git checkout feature/my-new-feature .
⚠️ Note: In newer Git versions (2.23+), git switch is the preferred command for changing branches, offering a clearer separation of concerns from git restore . E.g., git switch -c feature/my-new-feature to create and switch, or git switch feature/my-new-feature to switch.
Verifying Current Branch:
Always confirm you are on the correct branch before starting work.
Command: git branch (or git status ) will show your current branch, often highlighted with an asterisk.
Concept/Term
Memory Hook
Explanation
Example/Code
git clone
Imagine cloning a sheep – you get an exact, independent copy of the original, ready for your own modifications.
Downloads a complete copy of a remote Git repository, including all history and branches, to your local machine.
git clone https://github.com/user/repo.git
git pull
Think of it as a fishing net that both catches (fetches) new updates from the remote and pulls them into (merges) your local line.
Fetches changes from the remote repository and automatically merges them into the current local branch.
git pull origin main
git branch
Picture a tree trunk (main) from which new limbs (branches) sprout, allowing independent growth without affecting the main structure.
Creates a new branch pointer in the repository's history, allowing for isolated development.
git branch feature/new-login
git checkout
Imagine switching train tracks – you're moving your focus (HEAD) to a different line of development.
Switches the current working directory and HEAD pointer to a specified branch or commit.
git checkout feature/new-login
git checkout -b
This is like saying, "Create a new track AND immediately switch my train to it!" – a shortcut for efficiency.
Creates a new branch and immediately switches to it, combining two common operations.
git checkout -b bugfix/issue-123
# Scenario: Starting work on a new feature named 'user-profile-editing'
# 1. Clone the repository if you haven't already
# git clone https://github.com/my-org/my-app.git
# cd my-app
# 2. Ensure your main branch is up-to-date
git checkout main
git pull origin main
# 3. Create and switch to your new feature branch
git checkout -b feature/user-profile-editing
# 4. Verify you are on the correct branch
git branch
# Expected output:
# main
# * feature/user-profile-editing
📑 New Section
2. Feature Development Iteration: Staging and Committing
Photo by Mohammad Rahmani · unsplash
Once on your feature branch, you'll begin making code changes. Git's power lies in tracking these changes meticulously. The process involves modifying files, staging those changes, and then committing them to your branch's history.
Making Changes:
You'll edit, add, or delete files in your working directory. These changes are initially untracked or modified.
Checking the Status of Your Changes:
Before staging or committing, it's good practice to see what changes Git has detected.
Command: git status provides a summary of modified, staged, and untracked files. It's your dashboard for current changes.
Staging Changes ( git add ):
The staging area (also known as the index) is a crucial intermediate step. It allows you to select which specific changes you want to include in your next commit.
Explanation: git add takes changes from your working directory and places them into the staging area. This means only the changes you've "added" will be part of the subsequent commit, not all modifications in your working directory.
Example: Staging a specific file.
git add src/components/UserProfile.js
Example: Staging all changes in the current directory.
git add . (be cautious with this, ensure you review changes first)
Example: Staging specific hunks within a file.
git add -p src/components/UserProfile.js (interactive staging, very powerful for crafting precise commits).
Committing Changes ( git commit ):
A commit is a snapshot of your staged changes at a specific point in time. Each commit has a unique SHA-1 hash and a commit message.
Explanation: git commit creates a new commit object containing the staged changes and adds it to your branch's history. A good commit message is vital for auditing and understanding the project's evolution.
Commit Message Guidelines:
Start with a short, imperative subject line (under 50-72 characters) that completes the sentence "If applied, this commit will...".
Leave a blank line after the subject.
Provide a more detailed body explaining the what, why, and how of the changes, if necessary.
Example:
git commit -m "feat: Add user profile editing form" -m "Implements a basic form for users to update their name and email. Includes client-side validation."
💡 Pro-Tip: Frequent, small, atomic commits are generally preferred over large, monolithic ones. This makes history easier to review, revert, and understand.
Viewing Differences ( git diff ):
It's often useful to see exactly what changes you've made before staging or committing.
Command: git diff shows changes in the working directory that are not yet staged.
Command: git diff --staged (or git diff --cached ) shows changes in the staging area that are not yet committed.
Concept/Term
Memory Hook
Explanation
Example/Code
Working Directory
Imagine your desk where you're actively scribbling notes and making changes to your draft.
The actual files and directories on your file system that you are currently editing.
# Edit src/App.js
Staging Area (Index)
Think of it as a "to-be-committed" pile. You carefully select which notes from your desk you want to include in your next official document.
An intermediate area where you prepare changes before committing them. Only changes explicitly added here will be part of the next commit.
git add src/App.js
Repository (History)
This is your official, bound journal where each entry (commit) is a permanent, dated record of your work.
The database where Git stores all the project's history, including commits, branches, and tags.
git commit -m "Initial commit"
git status
Your personal Git assistant constantly checking your desk and "to-be-committed" pile, reporting what's changed.
Shows the state of the working directory and the staging area, indicating which files are modified, staged, or untracked.
git status
git add
The act of picking up a specific note from your desk and placing it into your "to-be-committed" pile.
Adds changes from the working directory to the staging area, preparing them for the next commit.
git add .
git commit
The moment you formally write an entry into your journal, sealing a set of changes with a descriptive note.
Records the staged changes as a new commit in the repository's history, along with a commit message.
git commit -m "feat: Add user login"
# Scenario: Developing a user profile editing feature
# (Assumes you are on 'feature/user-profile-editing' branch)
# 1. Make some changes to a file
# echo "function UserProfile() { /* ... */ }" > src/components/UserProfile.js
# echo "import UserProfile from './components/UserProfile';" >> src/App.js
# 2. Check the status of your changes
git status
# Expected output:
# Changes not staged for commit:
# modified: src/App.js
# Untracked files:
# src/components/UserProfile.js
# 3. Stage the new component file
git add src/components/UserProfile.js
git status
# Expected output:
# Changes to be committed:
# new file: src/components/UserProfile.js
# Changes not staged for commit:
# modified: src/App.js
# 4. Stage the modification to App.js
git add src/App.js
git status
# Expected output:
# Changes to be committed:
# new file: src/components/UserProfile.js
# modified: src/App.js
# 5. Commit the staged changes with a descriptive message
git commit -m "feat: Implement basic UserProfile component" -m "Adds a new component for user profiles and integrates it into App.js."
# 6. Verify the status (should be clean)
git status
# Expected output:
# On branch feature/user-profile-editing
# nothing to commit, working tree clean
📑 New Section
3. Advanced Branch Management Strategies
Photo by Hanna Morris · unsplash
Effective branch management is crucial for maintaining a clean, stable codebase and facilitating parallel development. Different types of branches serve specific purposes within a project's lifecycle.
Feature Branches:
Purpose: Used for developing new features, enhancements, or significant refactoring. They isolate work from the main codebase until it's complete and tested.
Lifecycle: Created from main or develop , worked on independently, and eventually merged back into the base branch.
Naming: Typically feature/<descriptive-name> or feat/<issue-id> .
💡 Pro-Tip: Keep feature branches focused on a single, cohesive unit of work. Avoid "mega-features" that become difficult to review and merge.
Bugfix Branches:
Purpose: Dedicated to fixing bugs identified in existing features.
Lifecycle: Similar to feature branches, but often created from the branch where the bug exists (e.g., develop or even main if it's a critical production bug).
Naming: Usually bugfix/<issue-id> or fix/<description> .
Hotfix Branches:
Purpose: Critical, immediate fixes for issues found in the production environment. These need to be deployed as quickly as possible.
Lifecycle: Created directly from the main (or master ) branch, fixed, tested, and then merged back into both main and develop (to ensure the fix is propagated to future releases).
Naming: Commonly hotfix/<description> or hotfix/<version> .
⚠️ Note: Hotfixes often bypass the standard development workflow to accelerate deployment, but still require rigorous testing before release.
Release Branches:
Purpose: Prepare a new production release. They allow for final bug fixes, documentation updates, and version bumping without affecting ongoing development in develop .
Lifecycle: Created from develop . Once stable, it's merged into main (and tagged with a version number) and also back into develop .
Naming: Typically release/<version-number> (e.g., release/1.0.0 ).
Viewing All Branches:
It's helpful to see all local and remote branches to understand the project's branching structure.
Command: git branch -a lists all local and remote-tracking branches.
Command: git branch -r lists only remote-tracking branches.
Deleting Branches:
Once a feature is merged and no longer needed, its branch should be deleted to keep the repository clean.
Command: git branch -d branch-name deletes a local branch. This command prevents deletion if the branch has unmerged changes.
Command: git branch -D branch-name forces deletion of a local branch, even if it has unmerged changes. Use with caution.
Command: git push origin --delete remote-branch-name deletes a branch from the remote repository.
Concept/Term
Memory Hook
Explanation
Example/Code
Feature Branch
Imagine a dedicated workshop where you build a new car engine, separate from the main assembly line, until it's perfect.
An isolated branch for developing a new feature, allowing independent work without disrupting the main codebase.
git checkout -b feature/dark-mode
Hotfix Branch
Think of it as an emergency repair crew rushing to fix a critical leak in the main pipeline, bypassing standard procedures for speed.
A branch created directly from the production branch ( main ) to quickly address critical bugs in live software.
git checkout -b hotfix/prod-bug-123 main
Release Branch
Picture a "staging area" for a movie premiere. All final edits, sound mixing, and color grading happen here before the film is released to the public.
A branch used to prepare for a new software release, allowing for final bug fixes and release-specific tasks without impacting ongoing development.
git checkout -b release/v1.1.0 develop
git branch -a
It's like asking your Git assistant to show you the blueprints for ALL the active projects and their remote counterparts.
Lists all local branches and all remote-tracking branches.
git branch -a
git branch -d
Carefully pruning a tree branch that has already borne fruit, ensuring it's not still actively growing or holding unpicked fruit.
Deletes a local branch, but only if it has been fully merged into its upstream branch.
git branch -d feature/old-feature
# Scenario: Managing various branches in a project
# 1. Create a feature branch and a hotfix branch
git checkout main
git pull origin main # Ensure main is up-to-date
git checkout -b feature/new-dashboard
# ... work on new-dashboard ...
git commit -am "feat: Initial dashboard layout"
git checkout main
git checkout -b hotfix/critical-login-bug
# ... work on hotfix ...
git commit -am "fix: Resolve login critical bug"
# 2. View all branches (local and remote)
git branch -a
# Expected output might include:
# * hotfix/critical-login-bug
# main
# feature/new-dashboard
# remotes/origin/main
# remotes/origin/feature/existing-feature
# 3. Simulate merging a feature branch (covered in next section)
# git checkout main
# git merge feature/new-dashboard --no-ff # Assuming it's merged
# 4. Delete the local feature branch after it's merged
# git branch -d feature/new-dashboard
# Expected output:
# Deleted branch feature/new-dashboard (was <commit-hash>).
# 5. Delete a remote branch (after it's merged and no longer needed)
# git push origin --delete feature/new-dashboard
📑 New Section
4. Integrating Changes: Merging and Rebasing
Photo by Meriç Dağlı · unsplash
Once a feature or fix is complete on its dedicated branch, the next step is to integrate those changes back into a main development line (e.g., main or develop ). Git offers two primary strategies for this: merging and rebasing.
Merging:
Explanation: Merging combines the history of two branches into a single, new commit. It preserves the exact history of commits from both branches.
Process:
Switch to the target branch (e.g., main or develop ).
Run git merge source-branch .
Types of Merges:
Fast-Forward Merge: Occurs when the target branch has not diverged from the source branch since the source branch was created. Git simply moves the target branch pointer forward to the latest commit of the source branch. No new merge commit is created.
3-Way Merge (Recursive Merge): Occurs when both branches have diverged from a common ancestor. Git creates a new "merge commit" that has two parent commits (one from each merged branch). This commit explicitly records the act of merging.
--no-ff option: Using git merge --no-ff source-branch forces a 3-way merge even if a fast-forward merge is possible. This creates a merge commit, preserving the branch history and making it clear when a feature branch was integrated. This is often preferred in collaborative workflows.
Example: Merging a feature branch into main .
git checkout main
git merge feature/my-new-feature --no-ff
Rebasing:
Explanation: Rebasing is the process of moving or combining a sequence of commits to a new base commit. It rewrites history by applying commits from one branch onto another one by one.
Process:
Switch to your feature branch.
Run git rebase target-branch (e.g., git rebase main ).
Result: Your feature branch's commits are replayed on top of the latest commit of the target branch, making it appear as if you started your work from the most up-to-date point. This creates a linear history.
Advantages:
Keeps commit history clean and linear, avoiding "merge bubbles."
Makes it easier to trace changes and revert commits.
Disadvantages:
Rewrites history: Never rebase commits that have already been pushed to a shared remote repository and that others might have based their work on. This can cause significant problems for collaborators.
Can be more complex to resolve conflicts during the rebase process.
💡 Pro-Tip: Use rebase to keep your feature branch up-to-date with main *before* merging. This makes the final merge a simple fast-forward (or a clean --no-ff merge).
Example: Rebasing a feature branch onto main .
git checkout feature/my-new-feature
git rebase main
Concept/Term
Memory Hook
Explanation
Example/Code
Merging
Imagine two separate rivers flowing into a single, wider river, where the point of confluence is clearly visible.
Combines the changes from one branch into another, creating a new merge commit that explicitly records the integration point. Preserves full history.
git checkout main
git merge feature/login
Fast-Forward Merge
Like a train track that simply extends forward because there are no diverging paths – no new switch (merge commit) is needed.
Occurs when the target branch has not diverged. The branch pointer simply moves forward to the latest commit of the source branch.
git checkout main
git merge feature/simple-fix (if main hasn't changed)
3-Way Merge
Two separate roads meet at a roundabout, and a new road (the merge commit) is built to connect them all.
Occurs when branches have diverged. Git creates a new commit with two parents to reconcile the histories.
git merge feature/complex-feature --no-ff
Rebasing
Think of taking your individual steps (commits) and re-walking them on a newly paved road (the updated base branch), making it look like you started there.
Rewrites commit history by moving a sequence of commits to a new base commit, creating a linear history.
git checkout feature/my-branch
git rebase main
--no-ff
Even if the train track is clear, you insist on building a new switch (merge commit) to explicitly mark where a new line joined.
Forces Git to create a merge commit even if a fast-forward merge is possible, preserving the branch's existence in history.
git merge feature/my-feature --no-ff
# Scenario: Integrating a feature branch into main
# Assume 'feature/user-profile-editing' has some commits
# And 'main' might have new commits from other developers
# 1. Update your main branch
git checkout main
git pull origin main
# 2. (Optional but recommended) Rebase your feature branch onto the latest main
# This makes your feature branch "up-to-date" with main before merging
git checkout feature/user-profile-editing
git rebase main
# If conflicts occur during rebase, resolve them, then:
# git add .
# git rebase --continue
# If you need to abort: git rebase --abort
# 3. Switch back to main
git checkout main
# 4. Merge the feature branch into main, forcing a merge commit
# This explicitly records the integration of the feature branch
git merge feature/user-profile-editing --no-ff
# 5. (Optional) Delete the feature branch locally after successful merge
git branch -d feature/user-profile-editing
# 6. Push the updated main branch to the remote
git push origin main
📑 New Section
5. Resolving Merge Conflicts
Photo by Romain Dancre · unsplash
Merge conflicts are an inevitable part of collaborative development. They occur when Git cannot automatically reconcile diverging changes between two branches. Understanding how to identify, resolve, and commit these resolutions is a critical skill.
When Conflicts Occur:
A conflict happens when the same line of code (or nearby lines) has been modified differently in two branches that you are trying to merge or rebase.
Git will pause the merge/rebase process and inform you of the conflicting files.
Example: Developer A changes line 10 of file.js to "Hello World", while Developer B changes the same line to "Goodbye World".
Identifying Conflict Markers:
When a conflict occurs, Git modifies the conflicting files in your working directory to include special markers.
These markers delineate the conflicting sections:
<<<<<<< HEAD : Marks the beginning of the changes from your current branch (HEAD).
======= : Separates the changes from your current branch from the incoming changes.
>>>>>>> branch-name : Marks the end of the incoming changes from the branch you are merging/rebasing.
Example: A conflicting file might look like this:
function greet() {
<<<<<<< HEAD
console.log("Hello from my-feature-branch");
=======
console.log("Hi from main branch");
>>>>>>> main
}
Resolving Conflicts Manually:
Open each conflicting file in your editor.
Manually edit the file to choose which changes to keep, or combine them as needed.
Remove all conflict markers ( <<<<<<< , ======= , >>>>>>> ).
The goal is to produce a single, correct version of the code.
💡 Pro-Tip: Use a graphical merge tool (e.g., VS Code's built-in merge editor, KDiff3, Meld) for complex conflicts. Configure it with git config --global merge.tool vscode and then run git mergetool .
Marking Conflicts as Resolved ( git add ):
After you have manually edited a conflicting file and removed all markers, you must tell Git that the conflict for that file is resolved.
Command: git add conflicting-file.js stages the resolved version of the file.
Repeat this for all conflicting files.
Completing the Merge/Rebase ( git commit / git rebase --continue ):
Once all conflicts are resolved and staged:
If you were merging: git commit will complete the merge. Git usually pre-populates a merge commit message; you can accept or modify it.
If you were rebasing: git rebase --continue will apply the next commit in the rebase sequence. If there are more conflicts, the process will pause again.
⚠️ Note: If you get stuck or realize you made a mistake during a merge, you can abort it with git merge --abort . For a rebase, use git rebase --abort . This will return your repository to its state before the merge/rebase attempt.
Checking Conflict Status:
During conflict resolution, git status is your best friend. It will list unmerged paths (conflicting files) and guide you through the remaining steps.
Concept/Term
Memory Hook
Explanation
Example/Code
Merge Conflict
Imagine two people trying to write in the same sentence of a shared document simultaneously, resulting in a jumbled mess that needs manual untangling.
Occurs when Git cannot automatically combine diverging changes to the same lines of code in two different branches.
git merge feature/dev-branch (outputs "CONFLICT")
Conflict Markers
Like temporary "caution tape" Git places around the disputed sections of your code, showing you exactly where the disagreement is.
Special strings ( <<<<<<< , ======= , >>>>>>> ) Git inserts into files to highlight conflicting sections.
<<<<<<< HEAD
my code
=======
their code
>>>>>>> branch-name
git mergetool
Your personal conflict mediator, opening a specialized side-by-side view to help you visually compare and choose changes.
Launches an external merge tool (if configured) to assist in resolving conflicts visually.
git mergetool
git add <file> (during conflict)
After you've manually fixed the jumbled sentence, you're telling Git, "Okay, this page is good now, put it in the 'resolved' pile."
Marks a conflicting file as resolved after you've manually edited it and removed conflict markers.
git add src/App.js
git merge --abort
Hitting the "undo" button on your merge attempt, returning your repository to its state before the merge started.
Aborts the current merge operation, discarding any partial merges and returning to the state before the merge command was run.
git merge --abort
git rebase --continue
After fixing a step in your re-walked journey, you tell Git, "Okay, that step is done, now apply the next one."
Continues a rebase operation after conflicts have been resolved and staged.
git rebase --continue
# Scenario: Resolving a merge conflict
# Assume you are on 'main' and 'feature/conflict-example' has conflicting changes
# 1. Attempt to merge the feature branch
git checkout main
git merge feature/conflict-example
# Git will output:
# Auto-merging src/data.js
# CONFLICT (content): Merge conflict in src/data.js
# Automatic merge failed; fix conflicts and then commit the result.
# 2. Check status to see conflicting files
git status
# Expected output:
# Unmerged paths:
# both modified: src/data.js
# 3. Open src/data.js and manually resolve the conflict
# (Example content of src/data.js after conflict)
# <<<<<<< HEAD
# const API_URL = "https://api.example.com/v1";
# =======
# const API_URL = "https://api.example.com/beta";
# >>>>>>> feature/conflict-example
# After manual resolution (e.g., choosing the 'beta' URL):
# const API_URL = "https://api.example.com/beta";
# 4. Stage the resolved file
git add src/data.js
# 5. Check status again (should show 'all conflicts fixed')
git status
# Expected output:
# All conflicts fixed but you are still merging.
# Changes to be committed:
# modified: src/data.js
# 6. Commit the merge
git commit
# Git will open an editor with a default merge commit message.
# Save and close the editor to complete the merge.
# 7. Verify merge is complete
git log --oneline --graph
# Expected output should show the merge commit.
📑 New Section
6. Auditing and History Inspection
Photo by Andres Siimon · unsplash
Understanding the history of a project is crucial for debugging, code reviews, and maintaining code quality. Git provides powerful tools to inspect every change, who made it, and when.
6.1. Viewing Commit History
The git log command is your primary tool for exploring the project's history. It shows a list of commits, starting from the most recent.
Scenario: Understanding the evolution of a feature or finding a specific change.
You need to see all commits, who authored them, and their messages. You might also want to filter by author, date, or content.
# View the full commit history (most recent first)
git log
# View a summarized history (one commit per line)
git log --oneline
# View a graphical representation of the commit history, including branches and merges
git log --oneline --graph --all
# View history with author, date, and full message
git log
# View history for a specific file
git log -- src/utils.js
# View history by a specific author
git log --author="Omar Yosr"
# View history since a specific date
git log --since="2 weeks ago"
git log --since="2023-01-01"
# View history containing a specific text in the commit message or code changes
git log -S"bug fix" # Searches for commits that added or removed "bug fix"
git log --grep="feature" # Searches for commits with "feature" in their message
# View history for a specific number of commits
git log -n 5 # Shows the last 5 commits
6.2. Inspecting Specific Commits
Once you've identified a commit of interest using git log , you can use git show to see the exact changes introduced by that commit.
Scenario: Reviewing changes made in a particular commit or understanding why a specific line of code was changed.
You have a commit hash (e.g., from git log --oneline ) and want to see what files were modified and the diff for each change.
# Assuming 'abcdef1' is a commit hash from git log
git show abcdef1
# To see only the changed files (without the diff)
git show --name-only abcdef1
# To see the stats (how many lines added/deleted) for the commit
git show --stat abcdef1
6.3. Comparing Changes (Diffing)
The git diff command is essential for comparing different states of your repository, whether it's between your working directory and the staging area, two commits, or two branches.
Scenario: Before committing, checking changes; comparing a feature branch with the main branch; or understanding differences between two versions of a file.
# Show changes in your working directory that are not yet staged
git diff
# Show changes in the staging area that are ready to be committed
git diff --staged
# Or:
git diff --cached
# Show changes between your working directory and the last commit
git diff HEAD
# Show changes between two specific commits
# (e.g., 'abcdef1' and 'fedcba9' are commit hashes)
git diff abcdef1 fedcba9
# Show changes between two branches
git diff feature-branch main
# Show changes for a specific file between two branches
git diff feature-branch main -- src/component.js
# Show changes between a commit and the current working directory
git diff abcdef1
# Show changes between the current branch and its upstream remote branch
git diff @{u}
6.4. Identifying Who Changed What (Blame)
The git blame command shows line-by-line what revision and author last modified each line of a file. It's incredibly useful for tracking down the origin of a bug or understanding code ownership.
Scenario: A bug is found on a specific line of code, and you need to know who introduced it and in which commit.
# Show who last modified each line of a file
git blame src/feature.js
# Show blame for a specific range of lines
git blame -L 10,20 src/feature.js # Lines 10 to 20
# Show blame with a different format (e.g., showing commit date)
git blame --date=relative src/feature.js
# Show blame for a file as it existed in a specific commit
git blame abcdef1 -- src/feature.js
📑 New Section
7. Undoing Changes and Reverting Mistakes
Photo by Kelly Sikkema · unsplash
Mistakes happen. Git provides several powerful commands to undo changes, revert commits, or even rewrite history. Understanding when to use each is crucial for maintaining a clean and accurate project history.
7.1. Unstaging Changes
If you've added files to the staging area (with git add ) but decide you don't want to include them in the next commit, you can unstage them.
Scenario: You accidentally staged a file or decided a change shouldn't be part of the upcoming commit.
# Stage a file (for demonstration)
git add src/new_feature.js
# Check status - it should show src/new_feature.js as staged
git status
# Unstage the file
git restore --staged src/new_feature.js
# Or for older Git versions:
# git reset HEAD src/new_feature.js
# Verify it's unstaged
git status
# Expected output:
# Changes to be committed:
# (nothing)
# Changes not staged for commit:
# modified: src/new_feature.js (if it was modified)
# Untracked files:
# src/new_feature.js (if it was a new file)
7.2. Discarding Local Changes
Sometimes you make changes to files in your working directory that you decide to completely discard. This effectively reverts the file to its last committed or staged state.
Scenario: You've been experimenting with code, but it's not working, and you want to revert a file back to its last clean state (either from the last commit or the staged version).
# Make some changes to a file
# (e.g., open src/data.js and add some random text)
# Check status - it should show src/data.js as modified
git status
# Discard all changes in src/data.js since the last commit/stage
git restore src/data.js
# Or for older Git versions:
# git checkout -- src/data.js
# Verify the changes are gone
git status
# Expected output:
# nothing to commit, working tree clean
# And the file src/data.js should be back to its previous content.
# To discard ALL local changes in the working directory (use with extreme caution!)
git restore .
# Or for older Git versions:
# git checkout .
7.3. Undoing Commits (Revert)
git revert creates a new commit that undoes the changes introduced by a previous commit. This is a "safe" way to undo changes because it preserves the project history, making it suitable for shared branches.
Scenario: A bug was introduced in a recent commit that has already been pushed to a shared remote repository. You need to undo that specific change without rewriting history.
# 1. View history to find the commit to revert
git log --oneline
# Let's assume 'abcdef1' is the commit you want to revert
# (e.g., 'abcdef1 Fix: Introduced critical bug in login flow')
# 2. Revert the commit
git revert abcdef1
# Git will open an editor with a default revert commit message.
# Save and close the editor. A new commit is created that undoes abcdef1.
# 3. Verify the new commit
git log --oneline
# Expected output:
# Revert "Fix: Introduced critical bug in login flow"
# abcdef1 Fix: Introduced critical bug in login flow
# ... (earlier commits)
# 4. Push the revert commit to the remote (if on a shared branch)
git push origin main
7.4. Undoing Commits (Reset)
git reset is a powerful command that moves the HEAD pointer and optionally changes the staging area and working directory. It effectively rewrites history and should be used with caution, especially on shared branches.
There are three main modes for git reset :
--soft : Moves HEAD, but keeps the staging area and working directory untouched. The changes from the reset commits are now staged.
--mixed (default): Moves HEAD, clears the staging area, but keeps the working directory untouched. The changes from the reset commits are now unstaged.
--hard : Moves HEAD, clears the staging area, and discards all changes in the working directory. This is destructive!
Scenario 1 ( --soft ): You made a commit, but immediately realized you forgot to add a small change or want to combine it with the previous commit.
You want to undo the commit but keep all changes staged so you can easily amend the commit or add more changes before committing again.
# 1. Make a commit (for demonstration)
echo "Some new feature code" > src/feature_a.js
git add src/feature_a.js
git commit -m "feat: Added feature A"
# 2. Realize you want to undo the commit but keep changes staged
git reset --soft HEAD~1
# HEAD~1 refers to the commit directly before the current HEAD.
# 3. Check status
git status
# Expected output:
# Changes to be committed:
# new file: src/feature_a.js
# The commit is gone, but the changes are still staged.
# You can now add more changes and `git commit --amend` or a new `git commit`.
Scenario 2 ( --mixed ): You made a commit, but it was premature, and you want to undo it and continue working on the changes as unstaged files.
This is the default behavior of git reset if no mode is specified.
# 1. Make a commit (for demonstration)
echo "Another feature implementation" > src/feature_b.js
git add src/feature_b.js
git commit -m "feat: Implemented feature B"
# 2. Realize the commit was premature and you want to undo it, keeping changes unstaged
git reset HEAD~1
# Or: git reset --mixed HEAD~1
# 3. Check status
git status
# Expected output:
# Changes not staged for commit:
# new file: src/feature_b.js
# The commit is gone, and the changes are now in your working directory, unstaged.
Scenario 3 ( --hard ): You've made a series of local commits or changes that you want to completely discard, reverting your branch and working directory to an earlier state.
USE WITH EXTREME CAUTION! This is destructive and will delete uncommitted changes in your working directory.
# 1. Make some changes and commits (for demonstration)
echo "Temporary change 1" > temp1.txt
git add temp1.txt
git commit -m "Temp commit 1"
echo "Temporary change 2" > temp2.txt
git add temp2.txt
git commit -m "Temp commit 2"
# Also have some uncommitted changes
echo "Uncommitted work" > uncommitted.txt
# 2. Decide to discard everything back to the state before "Temp commit 1"
# First, find the commit hash you want to reset TO.
# Let's say the commit before "Temp commit 1" was 'initial_commit_hash'.
git log --oneline
# Example:
# abcdef1 Temp commit 2
# fedcba9 Temp commit 1
# 1234567 Initial commit
# We want to reset to 1234567
git reset --hard 1234567
# 3. Verify
git status
# Expected output:
# On branch main
# nothing to commit, working tree clean
# Files temp1.txt, temp2.txt, and uncommitted.txt will be gone.
Important Note on Resetting Pushed Commits: Never use git reset --hard or any form of git reset that rewrites history on commits that have already been pushed to a shared remote branch. Doing so will cause conflicts for other developers when they pull, as their history will diverge from yours. For shared history, always use git revert .
📑 New Section
8. Branching Strategies and Collaboration
Photo by krakenimages · unsplash
Branching is a core concept in Git that allows developers to work on different features or fixes in isolation without affecting the main codebase. Effective branching strategies are crucial for team collaboration and managing complex projects.
8.1. Common Branching Models
While Git is flexible, several common branching models have emerged to streamline development workflows:
8.1.1. Git Flow (Feature Branch Workflow)
A more complex, but highly structured model, often used for projects with scheduled releases. It defines two main long-lived branches ( main / master and develop ) and several supporting branches ( feature , release , hotfix ).
main (or master ): Always production-ready, contains the last released version.
develop : Integrates all completed features, serves as the base for the next release.
feature branches: Created from develop for new features, merged back into develop .
release branches: Created from develop for release preparation (bug fixes, final tweaks), merged into main and develop .
hotfix branches: Created from main to quickly address critical production bugs, merged into main and develop .
Pros: Clear separation of concerns, robust for scheduled releases, well-defined roles for branches.
Cons: Can be overly complex for small teams or projects with continuous delivery, requires strict adherence.
8.1.2. GitHub Flow
A simpler, lightweight, and continuous delivery-oriented model. It has only one long-lived branch: main (or master ).
main (or master ): Always deployable.
feature branches: Created from main for any new work (features, bug fixes, experiments).
Work is done on feature branches, pushed to the remote, and pull requests are opened.
Once reviewed and approved, feature branches are merged directly into main and immediately deployed.
Pros: Simple, fast, ideal for continuous integration/delivery, fewer long-lived branches to manage. Cons: Requires strong test automation and frequent deployments, less structured for complex release cycles.
8.1.3. GitLab Flow
An extension of GitHub Flow, adding environment branches (e.g., production , staging , pre-production ) to manage deployments to different environments. It often uses feature branches that merge into a main branch, which then gets merged into environment-specific branches.
main (or master ): Integrates all new features.
feature branches: Created from main , merged back into main .
environment branches (e.g., production , staging ): main is merged into these branches to deploy to specific environments.
Pros: Balances simplicity with structured deployments, good for projects with multiple deployment environments. Cons: Can still become complex with many environment branches.
8.2. Creating and Managing Branches
Regardless of the branching model, the fundamental Git commands for branches remain the same.
Scenario: Starting a new feature, fixing a bug, or experimenting with an idea without affecting the main development line.
# 1. View all local branches
git branch
# 2. View all local and remote branches
git branch -a
# 3. Create a new branch (e.g., 'feature/user-profile')
# This creates the branch but does not switch to it.
git branch feature/user-profile
# 4. Switch to the new branch
git checkout feature/user-profile
# Or for Git 2.23+
git switch feature/user-profile
# 5. Create and switch to a new branch in one command
git checkout -b bugfix/login-issue
# Or for Git 2.23+
git switch -c bugfix/login-issue
# 6. Push a new local branch to the remote repository
# This creates a corresponding branch on the remote and sets up tracking.
git push -u origin feature/user-profile
# The -u (or --set-upstream) flag is only needed the first time.
# Subsequent pushes can just be: git push
# 7. Delete a local branch (after it's merged or no longer needed)
# Use -d for merged branches.
git branch -d feature/user-profile
# Use -D for unmerged branches (force delete). Use with caution!
git branch -D experimental-branch
# 8. Delete a remote branch
git push origin --delete feature/user-profile
# Or:
git push origin :feature/user-profile
8.3. Integrating Changes (Merge vs. Rebase)
Once work on a feature branch is complete, its changes need to be integrated back into the main development line (e.g., main or develop ). Git offers two primary methods: merging and rebasing.
8.3.1. Merging
Merging combines the history of two branches, creating a new "merge commit" that records the integration. It preserves the exact history of commits.
Scenario: Integrating a completed feature branch into the main branch, preserving the history of all commits on the feature branch.
# Assume you are on the 'main' branch and want to merge 'feature/user-profile'
git checkout main
# Ensure your main branch is up-to-date
git pull origin main
# Merge the feature branch into main
git merge feature/user-profile
# If there are conflicts, resolve them as described in section 5.
# Git will open an editor for the merge commit message. Save and close.
# Push the merged changes to the remote
git push origin main
8.3.2. Rebasing
Rebasing moves or combines a sequence of commits to a new base commit. It rewrites history by replaying commits from one branch onto another, resulting in a linear history without merge commits.
Scenario: Keeping a feature branch up-to-date with the main branch, or creating a clean, linear history before merging a feature branch.
Warning: Do not rebase commits that have already been pushed to a shared remote repository, as it rewrites history and can cause major issues for collaborators. Rebase only on local, unpushed branches.
# Assume you are on 'feature/user-profile' and want to rebase it onto 'main'
git checkout feature/user-profile
# Ensure your main branch is up-to-date (fetch, but don't merge/rebase yet)
git fetch origin main
# Rebase the current branch onto 'main'
git rebase main
# Git will replay your feature branch commits one by one on top of main.
# If conflicts occur, Git will pause the rebase.
# 1. Resolve the conflict.
# 2. git add <conflicted_files>
# 3. git rebase --continue
# Repeat until all conflicts are resolved and all commits are replayed.
# To abort a rebase: git rebase --abort
# After successful rebase, your feature branch history is now linear with main.
# You can then merge it into main with a fast-forward merge (no new merge commit).
git checkout main
git merge feature/user-profile
# This will likely be a fast-forward merge if main hasn't had new commits since the rebase.
# Push the updated main branch
git push origin main
When to use Merge vs. Rebase:
Feature
Merge
Rebase
History
Preserves original commit history, including merge commits.
Rewrites history, creating a linear history without merge commits.
Commit IDs
Original commit IDs are preserved.
New commit IDs are generated for replayed commits.
Safety on Shared Branches
Safe to use on shared branches (does not rewrite history).
Unsafe on shared branches (rewrites history, causes divergence).
Complexity
Simpler to understand and execute for beginners.
Can be more complex, especially with conflict resolution.
Use Case
When preserving exact history is important, or on shared branches.
When a clean, linear history is preferred, typically on local feature branches before merging.
8.4. Pull Requests (GitHub/GitLab Specific)
Pull Requests (or Merge Requests in GitLab) are a core collaboration feature of platforms like GitHub, GitLab, and Bitbucket. They provide a web-based interface for code review and discussion before merging changes.
Scenario: You've completed a feature on a branch and want your team to review it before it's integrated into the main codebase.
Create a Feature Branch:
git checkout -b feature/new-dashboard main
# ... make changes and commit ...
git push -u origin feature/new-dashboard
Open a Pull Request:
Go to your repository on GitHub/GitLab. You'll usually see a prompt to "Compare & pull request" (GitHub) or "Create merge request" (GitLab) for your newly pushed branch. Select your feature branch as the source and main (or develop ) as the target.
Fill in the title, description, assign reviewers, link issues, etc.
Code Review and Discussion:
Reviewers examine the changes, leave comments, suggest improvements. You might need to make additional commits to your feature branch based on feedback. These new commits will automatically appear in the open Pull Request.
# ... make more changes ...
git add .
git commit -m "refactor: Addressed review comments"
git push origin feature/new-dashboard
Merge the Pull Request:
Once approved, the Pull Request can be merged. Platforms often offer different merge options:
Merge Commit: Creates a merge commit, preserving all feature branch commits (like git merge ).
Squash and Merge: Combines all feature branch commits into a single new commit on the target branch (good for keeping main history clean).
Rebase and Merge: Replays feature branch commits onto the target branch, creating a linear history (like git rebase ).
After merging, the feature branch can typically be safely deleted (often an option provided by the platform).
📑 New Section
9. Git Hooks and Automation
Photo by Sufyan · unsplash
Git hooks are scripts that Git executes automatically before or after events like committing, pushing, or receiving. They are a powerful way to automate tasks, enforce policies, and integrate with external tools.
9.1. Understanding Git Hooks
Git hooks are simple scripts (e.g., shell scripts, Python, Ruby) located in the .git/hooks/ directory of your repository. Each script corresponds to a specific Git event. When Git encounters that event, it checks for a script with the corresponding name and executes it.
There are two main types of hooks:
Client-side hooks: Run on the local developer's machine (e.g., pre-commit , post-commit , pre-rebase ). They can be easily bypassed by developers and are not shared automatically.
Server-side hooks: Run on the remote Git server (e.g., pre-receive , post-receive , update ). These are crucial for enforcing project-wide policies, like ensuring commit messages follow a standard or preventing direct pushes to main .
9.2. Common Use Cases for Git Hooks
9.2.1. Client-Side Hooks
pre-commit : Runs before a commit is created.
Linting code (e.g., ESLint, Black, Prettier)
Running unit tests
Checking commit message format
Ensuring no debug statements or sensitive information are committed
If this hook exits with a non-zero status, the commit is aborted.
prepare-commit-msg : Runs before the commit message editor is launched.
Automatically populating the commit message with a template or issue tracker ID.
post-commit : Runs after a commit is successfully created.
Triggering notifications
Updating a task tracker
Running integration tests (less common here, usually done by CI)
pre-push : Runs before git push .
Running tests that might be too slow for pre-commit
Ensuring the branch is up-to-date with the remote
Preventing pushes to certain branches without a pull request
If this hook exits with a non-zero status, the push is aborted.
9.2.2. Server-Side Hooks
pre-receive : Runs when a push is received by the server, before any references are updated.
Enforcing branch naming conventions
Preventing direct pushes to protected branches (e.g., main , develop )
Validating commit messages across the entire push
If this hook exits with a non-zero status, the entire push is rejected.
post-receive : Runs after a successful push, after all references have been updated.
Triggering CI/CD pipelines (e.g., Jenkins, GitLab CI, GitHub Actions)
Updating external systems (e.g., project management tools)
Notifying team members about new pushes
Deploying code to a staging or production environment
9.3. Implementing a Simple Pre-commit Hook
Let's create a simple pre-commit hook that checks for a specific keyword in the commit message.
Scenario: Enforce that every commit message must contain an issue ID like "FIX-123" or "FEAT-456".
Navigate to the hooks directory:
cd .git/hooks/
Rename the example hook (or create a new one):
mv pre-commit.sample pre-commit
(If pre-commit.sample doesn't exist, create a new file named pre-commit .)
Edit the pre-commit file:
Open .git/hooks/pre-commit in your text editor and add the following script:
#!/bin/sh
# This hook checks if the commit message contains an issue ID (e.g., "FIX-123" or "FEAT-456").
# Get the proposed commit message file
COMMIT_MSG_FILE=
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
# Define the regex pattern for issue IDs
# This pattern looks for words starting with 3-5 uppercase letters followed by a hyphen and 1-4 digits.
ISSUE_ID_PATTERN="[A-Z]{3,5}-[0-9]{1,4}"
if ! echo "$COMMIT_MSG" | grep -qE "$ISSUE_ID_PATTERN"; then
echo "ERROR: Commit message must contain an issue ID (e.g., FIX-123, FEAT-456)."
echo "Commit aborted."
exit 1
fi
exit 0
Make the hook executable:
chmod +x pre-commit
Test the hook:
# Create a dummy file and stage it
echo "test" > test.txt
git add test.txt
# Try to commit without an issue ID (should fail)
git commit -m "Initial commit without ID"
# Expected output: ERROR: Commit message must contain an issue ID... Commit aborted.
# Try to commit with an issue ID (should succeed)
git commit -m "FEAT-123: Initial commit with ID"
# Expected output: [main (root-commit) 1234567] FEAT-123: Initial commit with ID
9.4. Sharing Git Hooks
Client-side hooks are not automatically versioned or shared with the repository. To share them across a team, common strategies include:
Husky (npm package): For JavaScript/Node.js projects, Husky makes it easy to manage and share Git hooks via package.json .
Pre-commit framework (Python): A language-agnostic framework for managing and maintaining multi-language pre-commit hooks.
Custom script: A script that copies hooks from a versioned directory (e.g., .githooks/ ) into .git/hooks/ upon cloning or setup.
Server-side hooks are typically managed directly on the Git server and are automatically applied to all pushes to that repository.
📑 New Section
10. Advanced Git Concepts (Brief Overview)
Photo by Amir Balam · unsplash
Beyond daily operations, Git offers more advanced features for complex scenarios, project maintenance, and history manipulation.
10.1. Interactive Rebase
git rebase -i allows you to interactively modify a series of commits. You can reorder, squash (combine), edit, or drop commits.
Scenario: Cleaning up a messy feature branch history before merging, or combining several small commits into a logical one.
# Start an interactive rebase for the last 3 commits
git rebase -i HEAD~3
# Git will open an editor with a list of commits and commands:
# pick abcde12 Commit message 1
# pick fghij34 Commit message 2
# pick klmno56 Commit message 3
# Commands you can use:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
# l, label = label current HEAD with a name
# t, treat = like "pick" but create a new commit with the same tree as the original
# m, merge = use commit, but combine with previous commit (deprecated, use squash/fixup)
# Example: Squash the last two commits into the first one and reword it
# pick abcde12 Initial feature commit
# squash fghij34 Minor fix
# fixup klmno56 Typo correction
# Save and close the editor. Git will then apply the changes.
# If you used 'reword', another editor will open for the new commit message.
Again, remember: Do not use interactive rebase on commits that have already been pushed to a shared remote repository.
10.2. Git Stash
git stash temporarily saves changes you've made to your working directory and staging area, allowing you to switch branches or perform other tasks without committing incomplete work. It's like a temporary shelf for your changes.
Scenario: You're working on a feature, but a critical bug needs an immediate fix on another branch. You don't want to commit your incomplete work.
# 1. Make some changes to files
# (e.g., modify src/feature.js, create new_file.js)
# 2. Check status - you have uncommitted changes
git status
# 3. Stash your changes
git stash save "Work in progress on new feature"
# Or simply: git stash (if you don't need a message)
# 4. Your working directory is now clean
git status
# Expected output: nothing to commit, working tree clean
# 5. Switch to the bugfix branch, fix the bug, commit, and push
git checkout bugfix/critical-issue
# ... fix bug ...
git commit -m "fix: Critical bug fixed"
git push origin bugfix/critical-issue
# 6. Switch back to your feature branch
git checkout feature/your-feature
# 7. Apply your stashed changes
git stash pop
# 'pop' applies the latest stash and removes it from the stash list.
# If conflicts occur, resolve them and then `git add .` and `git stash drop` manually.
# To see a list of stashes:
git stash list
# To apply a specific stash without dropping it:
git stash apply stash@{1} # Applies the second stash in the list
# To drop a specific stash:
git stash drop stash@{1}
# To clear all stashes (use with caution!):
git stash clear
10.3. Git Bisect
git bisect is a powerful debugging tool that helps you find the commit that introduced a bug by performing a binary search through your commit history.
Scenario: A bug was reported, and you know it wasn't present in an older version, but it exists now. You need to pinpoint the exact commit that caused it.
# 1. Start the bisect session
git bisect start
# 2. Mark the current (buggy) commit as "bad"
git bisect bad
# 3. Find a known good commit (an older commit where the bug was NOT present)
# You might use git log to find a suitable commit hash.
git bisect good <good_commit_hash>
# Example: git bisect good 1234567
# Git will now automatically checkout a commit halfway between good and bad.
# You then test the code at this commit.
# 4. Mark the current commit as "good" or "bad"
# If the bug is present:
git bisect bad
# If the bug is NOT present:
git bisect good
# Git will continue to checkout commits, narrowing down the search.
# Repeat step 4 until Git finds the first bad commit.
# 5. Git will report the first bad commit
# Example output:
# abcdef1 is the first bad commit
# Author: John Doe <john.doe@example.com>
# Date: Mon Jan 1 12:00:00 2023 +0000
# feat: Introduced new login logic
# 6. End the bisect session and return to your original branch
git bisect reset
10.4. Git Reflog
The reflog (reference log) is a local history of all the HEAD movements in your repository. It tracks where your HEAD has been, even if you've rewritten history or accidentally deleted branches. It's a safety net for recovering lost commits.
Scenario: You accidentally deleted a branch, performed a destructive reset, or lost track of a commit.
# View the reflog
git reflog
# Expected output:
# abcdef1 HEAD@{0}: commit: feat: Added new feature
# fedcba9 HEAD@{1}: checkout: moving from bugfix to main
# 1234567 HEAD@{2}: commit (initial): Initial project setup
# ...
# Recover a lost commit or branch:
# If you accidentally reset --hard and lost commits, you can find the commit hash
# in the reflog and then create a new branch from it:
git branch recovered-feature abcdef1 # (where abcdef1 is the commit you want to recover)
# If you deleted a branch, find the last commit it pointed to in the reflog
# and recreate the branch from that commit.
The reflog is your local safety net; it's not shared with remote repositories.
📑 New Section
Conclusion
Git and GitHub are indispensable tools for modern software development. From basic daily tasks like committing and pushing to advanced scenarios like conflict resolution, branching strategies, and history inspection, mastering these tools empowers developers to work efficiently, collaboratively, and with confidence. By understanding the different use cases and commands, you can leverage Git's full potential to manage your projects effectively and contribute seamlessly within a team environment.
This guide has covered a broad spectrum of Git and GitHub functionalities, providing practical examples for common developer workflows. Continuous learning and hands-on practice are key to becoming proficient. Happy Gitting!