Back to Posts
DevOps #Git #Version Control #Collaboration

Git Version Control Essentials

Essential Git commands and workflows for modern software development.

5 min read
Git Version Control Essentials

Git Version Control Essentials

Introduction: Imagine working on a critical project and accidentally deleting hours of work, or trying to collaborate with a team where everyone's changes constantly overwrite each other's code. These scenarios are nightmares that version control systems were designed to prevent. Git has become the industry standard for managing code changes, enabling millions of developers worldwide to collaborate effectively and maintain a complete history of their projects.

Git is a free and open-source distributed version control system designed to handle everything from small to very large projects with speed and efficiency. Created by Linus Torvalds in 2005, Git has revolutionized how developers work together, making it possible to experiment freely, collaborate seamlessly, and maintain a reliable record of every change made to a codebase.


Table of Contents

  1. Why Version Control Matters
  2. Understanding Git Fundamentals
  3. Setting Up Git
  4. Basic Git Workflow
  5. Working with Branches
  6. Merging vs Rebasing
  7. Collaborating with Remote Repositories
  8. Essential Git Commands
  9. Common Workflows
  10. Undoing Changes
  11. Best Practices
  12. Troubleshooting Common Issues

Why Version Control Matters

Before diving into Git commands and workflows, it's important to understand why version control is essential in modern software development.

The Problem Without Version Control

Without version control, developers face numerous challenges:

  • Lost work: Accidental deletions or overwrites with no way to recover
  • Collaboration chaos: Multiple people editing the same files simultaneously
  • No history: Unable to see what changed, when, or why
  • Fear of experimentation: Afraid to try new approaches because you might break everything
  • Deployment nightmares: Difficulty tracking which version is in production

How Git Solves These Problems

Git addresses these challenges by providing:

Complete History: Every change is recorded with who made it, when, and why. You can review the entire evolution of your project and understand the reasoning behind decisions made months or years ago.

Safe Experimentation: Create branches to try new features without affecting the main codebase. If the experiment fails, simply delete the branch. If it succeeds, merge it back in.

Collaboration Support: Multiple developers can work on different features simultaneously without conflicts. Git intelligently merges changes and alerts you when manual intervention is needed.

Backup and Recovery: With Git, your code exists in multiple locations. Even if your local machine fails, the code is safe in remote repositories. You can recover any previous version of any file at any time.

Code Review and Quality: Git enables code review processes where team members can examine changes before they're merged into the main codebase, improving code quality and knowledge sharing.


Understanding Git Fundamentals

Before working with Git commands, let's understand the core concepts that make Git powerful.

Distributed vs Centralized

Unlike older version control systems (like SVN), Git is distributed. This means:

  • Every developer has a complete copy of the entire repository history
  • You can work offline and commit changes locally
  • No single point of failure
  • Faster operations since most commands work on local data

The Three States

Git has three main states that your files can be in:

Modified: You've changed the file but haven't committed it to your database yet.

# You edit a file - it's now modified
vim index.js

Staged: You've marked a modified file in its current version to go into your next commit.

# Stage the file - it's now staged
git add index.js

Committed: The data is safely stored in your local database.

# Commit the file - it's now committed
git commit -m "Update homepage functionality"

The Three Sections

Correspondingly, Git has three main sections:

Working Directory: The files you see and edit in your project folder. This is a single checkout of one version of the project, extracted from the compressed database in the Git directory.

Staging Area (Index): A file that stores information about what will go into your next commit. Think of it as a preview of your next commit.

Git Directory (Repository): Where Git stores the metadata and object database for your project. This is the most important part of Git, and it's what's copied when you clone a repository.

Understanding Commits

A commit is a snapshot of your project at a specific point in time. Each commit:

  • Has a unique identifier (SHA-1 hash)
  • Contains the changes made to files
  • Includes metadata (author, date, commit message)
  • Points to its parent commit(s), creating a history chain

Think of commits as save points in a video game. You can always return to any save point to see what your project looked like at that moment.


Setting Up Git

Before you can start using Git, you need to install and configure it properly.

Installation

macOS:

# Using Homebrew
brew install git

# Or download from git-scm.com

Linux:

# Ubuntu/Debian
sudo apt-get install git

# Fedora
sudo dnf install git

Windows:

Download Git for Windows from git-scm.com, which includes Git Bash, a terminal emulator.

Initial Configuration

After installation, configure your identity. This information will be included in every commit you make.

# Set your name
git config --global user.name "Your Name"

# Set your email
git config --global user.email "your.email@example.com"

# Set default branch name
git config --global init.defaultBranch main

# Set default editor
git config --global core.editor "code --wait"

Verify Configuration

# View all settings
git config --list

# View a specific setting
git config user.name

Setting up SSH keys allows you to connect to remote repositories without entering your password every time.

# Generate SSH key
ssh-keygen -t ed25519 -C "your.email@example.com"

# Start SSH agent
eval "$(ssh-agent -s)"

# Add your key to the agent
ssh-add ~/.ssh/id_ed25519

# Copy public key to clipboard (macOS)
pbcopy < ~/.ssh/id_ed25519.pub

Then add the public key to your GitHub/GitLab/Bitbucket account settings.


Basic Git Workflow

The basic Git workflow follows a simple pattern that you'll use hundreds of times. Let's walk through each step in detail.

1. Initialize or Clone a Repository

Starting a new project:

# Create a new directory
mkdir my-project
cd my-project

# Initialize Git repository
git init

This creates a .git directory that contains all the repository metadata.

Working on an existing project:

# Clone a repository
git clone https://github.com/username/repository.git

# Clone with a different folder name
git clone https://github.com/username/repository.git my-folder

2. Check Status

Before making changes, it's good practice to check the current state of your repository.

git status

This shows:

  • Which branch you're on
  • Which files have been modified
  • Which files are staged for commit
  • Untracked files

3. Make Changes

Edit your files using your preferred editor. Git will detect these changes.

# Create a new file
echo "# My Project" > README.md

# Edit an existing file
vim index.js

4. Stage Changes

Staging allows you to selectively choose which changes to include in your next commit.

# Stage a specific file
git add README.md

# Stage all changes in current directory
git add .

# Stage all changes in the repository
git add -A

# Stage specific files with pattern
git add *.js

# Stage parts of a file interactively
git add -p

Why staging? Staging gives you control over what goes into each commit, allowing you to create logical, focused commits even if you've made changes to multiple files.

5. Commit Changes

A commit saves the staged changes to the repository history.

# Commit with a message
git commit -m "Add README and update homepage"

# Commit with detailed message (opens editor)
git commit

# Stage and commit modified tracked files in one step
git commit -am "Quick update to existing files"

Writing good commit messages:

# Good commit messages
git commit -m "Add user authentication feature"
git commit -m "Fix navigation menu on mobile devices"
git commit -m "Update dependencies to latest versions"

# Bad commit messages
git commit -m "Fixed stuff"
git commit -m "Changes"
git commit -m "asdf"

6. View History

# View commit history
git log

# Compact one-line format
git log --oneline

# Show graph of branches
git log --oneline --graph --all

# Show last 5 commits
git log -5

# Show commits by specific author
git log --author="John"

7. Push to Remote

After committing locally, push your changes to a remote repository.

# Push to remote repository
git push origin main

# Push and set upstream
git push -u origin main

# Push all branches
git push --all

Working with Branches

Branches are one of Git's most powerful features, allowing you to diverge from the main line of development and work independently.

Understanding Branches

Think of branches as parallel universes for your code. You can create a branch, make changes, and then merge those changes back or discard them entirely.

Why Use Branches?

Feature Development: Develop new features without affecting the stable main branch.

Bug Fixes: Fix bugs in isolation and test thoroughly before merging.

Experimentation: Try radical changes without risk.

Collaboration: Multiple developers can work on different features simultaneously.

Basic Branch Operations

Create a new branch:

# Create a new branch
git branch feature-login

# Create and switch to new branch
git checkout -b feature-login

# Newer syntax (Git 2.23+)
git switch -c feature-login

List branches:

# List local branches
git branch

# List all branches (including remote)
git branch -a

# List with last commit info
git branch -v

Switch branches:

# Switch to existing branch
git checkout main

# Newer syntax
git switch main

Delete branches:

# Delete merged branch
git branch -d feature-login

# Force delete unmerged branch
git branch -D feature-login

# Delete remote branch
git push origin --delete feature-login

Branch Naming Conventions

Good branch names are descriptive and follow a consistent pattern:

# Feature branches
git checkout -b feature/user-authentication
git checkout -b feature/payment-integration

# Bug fix branches
git checkout -b fix/navbar-mobile-layout
git checkout -b bugfix/memory-leak

# Hotfix branches
git checkout -b hotfix/security-vulnerability

# Release branches
git checkout -b release/v1.2.0

Viewing Branch Differences

# Show differences between branches
git diff main feature-login

# Show files that differ
git diff --name-only main feature-login

# Show commit differences
git log main..feature-login

Merging vs Rebasing

Two primary ways to integrate changes from one branch into another are merging and rebasing. Understanding when to use each is crucial.

Merging

Merging creates a new "merge commit" that ties together the histories of both branches.

How to merge:

# Switch to the branch you want to merge into
git checkout main

# Merge the feature branch
git merge feature-login

What happens:

  • Git creates a new commit with two parents
  • Both branch histories are preserved
  • Creates a "merge commit" in the history

Advantages:

  • Preserves complete history
  • Safe and non-destructive
  • Shows when features were integrated
  • Easy to understand

Disadvantages:

  • Can create a cluttered history with many merge commits
  • History can become hard to follow with many parallel branches

When to use:

  • When working on public branches
  • When you want to preserve the context of when branches were merged
  • When working with others who have already pulled the branch

Rebasing

Rebasing moves the entire feature branch to begin on the tip of another branch, creating a linear history.

How to rebase:

# While on feature branch
git checkout feature-login
git rebase main

# Or in one command
git rebase main feature-login

What happens:

  • Git replays your commits on top of another branch
  • Each commit gets a new hash (they're technically new commits)
  • Creates a linear, cleaner history

Advantages:

  • Clean, linear project history
  • Easier to navigate and understand
  • No merge commits cluttering the log

Disadvantages:

  • Rewrites history (changes commit hashes)
  • Can be dangerous if used on public branches
  • More complex conflict resolution

When to use:

  • On local branches that haven't been pushed
  • To clean up local history before merging
  • When you want a linear history

Interactive Rebase

Interactive rebase is powerful for cleaning up commit history before sharing.

# Rebase last 5 commits interactively
git rebase -i HEAD~5

This allows you to:

  • pick: Use the commit as-is
  • reword: Change commit message
  • edit: Amend the commit
  • squash: Combine with previous commit
  • drop: Remove the commit

Example:

pick a1b2c3d Add login form
squash d4e5f6g Fix typo in login
pick g7h8i9j Add logout functionality
reword j0k1l2m Update user profile

The Golden Rule

Never rebase public branches. Once you've pushed commits to a shared repository and others might have based work on them, don't rebase. Use merge instead.


Collaborating with Remote Repositories

Remote repositories enable collaboration with other developers and serve as backups for your code.

Understanding Remotes

A remote is a version of your repository hosted on the internet or network. You can have multiple remotes.

View remotes:

# List remotes
git remote

# List remotes with URLs
git remote -v

# Show detailed remote info
git remote show origin

Adding Remotes

# Add a remote
git remote add origin https://github.com/username/repo.git

# Add additional remote
git remote add upstream https://github.com/original/repo.git

Fetching and Pulling

Fetch: Downloads changes from remote but doesn't merge them.

# Fetch from origin
git fetch origin

# Fetch from all remotes
git fetch --all

# Fetch and prune deleted branches
git fetch -p

Pull: Fetches and merges changes in one step.

# Pull from current branch's upstream
git pull

# Pull with rebase instead of merge
git pull --rebase

# Pull from specific remote and branch
git pull origin main

Pushing Changes

# Push to origin remote, main branch
git push origin main

# Push and set upstream tracking
git push -u origin feature-branch

# Push all branches
git push --all

# Push tags
git push --tags

# Force push (dangerous!)
git push --force
# Safer force push
git push --force-with-lease

Tracking Branches

Tracking branches are local branches that have a direct relationship to a remote branch.

# Create tracking branch
git checkout -b feature origin/feature

# Set upstream for existing branch
git branch --set-upstream-to=origin/main main

# View tracking relationships
git branch -vv

Essential Git Commands

Here's a comprehensive reference of essential Git commands you'll use regularly.

Status and Information

# Check repository status
git status

# Short status
git status -s

# Show commit history
git log

# Show graph of branches
git log --graph --oneline --all

# Show who changed what in a file
git blame filename.js

# Show changes in a commit
git show commit-hash

Making Changes

# Stage files
git add file.js
git add .
git add -A

# Unstage files
git reset HEAD file.js

# Commit changes
git commit -m "message"
git commit --amend

# Remove files
git rm file.js

# Move/rename files
git mv old.js new.js

Viewing Differences

# Show unstaged changes
git diff

# Show staged changes
git diff --staged

# Show changes between commits
git diff commit1 commit2

# Show changes in a file
git diff filename.js

Working with Branches

# Create branch
git branch branch-name

# Switch branch
git checkout branch-name
git switch branch-name

# Create and switch
git checkout -b branch-name

# Delete branch
git branch -d branch-name

# Merge branch
git merge branch-name

Remote Operations

# Clone repository
git clone url

# Fetch changes
git fetch origin

# Pull changes
git pull

# Push changes
git push origin branch-name

# View remotes
git remote -v

Stashing

Save changes temporarily without committing.

# Stash changes
git stash

# Stash with message
git stash save "work in progress"

# List stashes
git stash list

# Apply last stash
git stash apply

# Apply and remove stash
git stash pop

# Apply specific stash
git stash apply stash@{0}

# Drop stash
git stash drop stash@{0}

# Clear all stashes
git stash clear

Common Workflows

Feature Branch Workflow

# 1. Update main branch
git checkout main
git pull origin main

# 2. Create feature branch
git checkout -b feature/new-feature

# 3. Make changes and commit
git add .
git commit -m "Implement new feature"

# 4. Push feature branch
git push -u origin feature/new-feature

# 5. Create pull request on GitHub/GitLab
# (Done through web interface)

# 6. After review and approval, merge on platform
# or merge locally:
git checkout main
git pull origin main
git merge feature/new-feature

# 7. Delete feature branch
git branch -d feature/new-feature
git push origin --delete feature/new-feature

Gitflow Workflow

A more structured workflow for release management.

Main branches:

  • main: Production-ready code
  • develop: Integration branch for features

Supporting branches:

  • feature/*: New features
  • release/*: Release preparation
  • hotfix/*: Emergency production fixes
# Start new feature
git checkout develop
git checkout -b feature/new-feature

# Finish feature
git checkout develop
git merge feature/new-feature
git branch -d feature/new-feature

# Start release
git checkout develop
git checkout -b release/1.0.0

# Finish release
git checkout main
git merge release/1.0.0
git tag -a v1.0.0
git checkout develop
git merge release/1.0.0
git branch -d release/1.0.0

Forking Workflow

Common in open-source projects.

# 1. Fork repository on GitHub

# 2. Clone your fork
git clone https://github.com/yourusername/repo.git

# 3. Add upstream remote
git remote add upstream https://github.com/original/repo.git

# 4. Create feature branch
git checkout -b feature/contribution

# 5. Make changes and commit
git add .
git commit -m "Add contribution"

# 6. Push to your fork
git push origin feature/contribution

# 7. Create pull request on GitHub

# 8. Keep your fork updated
git fetch upstream
git checkout main
git merge upstream/main
git push origin main

Undoing Changes

Mistakes happen. Here's how to undo them safely.

Unstage Files

# Unstage specific file
git reset HEAD file.js

# Unstage all files
git reset HEAD

Discard Changes in Working Directory

# Discard changes to a file
git checkout -- file.js

# Discard all changes
git checkout -- .

# Newer syntax
git restore file.js

Amend Last Commit

# Change commit message
git commit --amend -m "New message"

# Add forgotten files to last commit
git add forgotten.js
git commit --amend --no-edit

Undo Commits

# Undo last commit, keep changes staged
git reset --soft HEAD~1

# Undo last commit, keep changes unstaged
git reset HEAD~1

# Undo last commit, discard changes
git reset --hard HEAD~1

# Undo multiple commits
git reset --hard HEAD~3

Revert Commits

Creates new commits that undo previous commits (safer for shared branches).

# Revert last commit
git revert HEAD

# Revert specific commit
git revert commit-hash

# Revert without committing immediately
git revert -n commit-hash

Recover Lost Commits

# View reflog
git reflog

# Recover lost commit
git checkout commit-hash

# Create branch from lost commit
git checkout -b recovered-branch commit-hash

Best Practices

Commit Best Practices

Commit often: Small, frequent commits are better than large, infrequent ones.

Write meaningful messages: Your future self will thank you.

# Good
git commit -m "Add password validation to login form"

# Bad
git commit -m "stuff"

Commit complete, logical changes: Each commit should represent one logical change.

Branch Best Practices

Use descriptive branch names: feature/user-authentication not fix-1

Keep branches short-lived: Merge or delete branches regularly

Don't commit to main directly: Always use feature branches

Collaboration Best Practices

Pull before you push: Always fetch and merge before pushing

Review before merging: Use pull requests for code review

Communicate: Let teammates know about major changes

Security Best Practices

Never commit sensitive data: Passwords, API keys, secrets

Use .gitignore: Exclude files that shouldn't be tracked

# .gitignore example
node_modules/
.env
*.log
.DS_Store

Review changes before committing: Use git diff to check what you're committing


Troubleshooting Common Issues

Merge Conflicts

When Git can't automatically merge changes:

# 1. Git will mark conflict in file
<<<<<<< HEAD
your changes
=======
their changes
>>>>>>> branch-name

# 2. Edit the file to resolve
# 3. Stage resolved file
git add conflicted-file.js

# 4. Complete merge
git commit

Accidentally Committed to Wrong Branch

# 1. Copy commit hash
git log --oneline

# 2. Switch to correct branch
git checkout correct-branch

# 3. Cherry-pick the commit
git cherry-pick commit-hash

# 4. Go back and remove from wrong branch
git checkout wrong-branch
git reset --hard HEAD~1

Pushed Sensitive Data

# 1. Remove file
git rm --cached sensitive-file

# 2. Add to .gitignore
echo "sensitive-file" >> .gitignore

# 3. Commit removal
git commit -m "Remove sensitive file"

# 4. Force push (if repository is not shared)
git push --force

# 5. Consider repository as compromised
# Rotate any exposed credentials immediately

Need to Change Author Info

# Change author of last commit
git commit --amend --author="Name <email@example.com>"

# Change author of multiple commits (interactive rebase)
git rebase -i HEAD~5
# Mark commits as 'edit', then for each:
git commit --amend --author="Name <email@example.com>"
git rebase --continue

Conclusion

Git is an incredibly powerful tool that becomes more intuitive with practice. While the learning curve can feel steep initially, mastering Git fundamentals will make you a more effective developer and enable seamless collaboration with teams worldwide.

The key is to start simple. Use the basic workflow daily, experiment with branches, and gradually incorporate more advanced features as you become comfortable. Don't be afraid to make mistakes in your local repository—that's what Git is designed to handle.

Next Steps

  1. Practice daily: Use Git for all your projects, even personal ones
  2. Experiment: Create test repositories to try new commands safely
  3. Read commit histories: Learn from how others structure their commits
  4. Contribute to open source: Apply your skills in real-world projects
  5. Learn advanced features: Explore cherry-pick, bisect, and submodules

Additional Resources

  • Official Documentation: https://git-scm.com/doc
  • Pro Git Book: Free comprehensive guide
  • Git Cheat Sheet: Quick reference for common commands
  • GitHub Learning Lab: Interactive tutorials
  • Visualizing Git: Tools to understand Git concepts visually

Remember, every expert Git user was once a beginner. The difference is practice and persistence. Keep committing, keep learning, and Git will become second nature.

Back to All Posts