Fundamentals 9 min read

Definitive Guide to Version Control

Published on November 24, 2025

Definitive Guide to Version Control

Version control isn’t optional. It’s essential. I’ve seen developers lose days or weeks of work by not using Git correctly. I’ve seen teams struggling with merge conflicts because they don’t follow a consistent workflow.

In this article, I’ll share the professional Git workflow that will help you work safely, collaboratively, and efficiently.

Why is Git essential?

Git isn’t just for saving code. It’s for:

  • History: See what changed and when
  • Collaboration: Multiple developers working without conflicts
  • Safety: Never lose code
  • Experiments: Try things without fear
  • Rollback: Revert problematic changes

Initial setup

Configure Git

# Configure name and email
git config --global user.name "Your Name"
git config --global user.email "you@email.com"

# Preferred editor
git config --global core.editor "code --wait"  # VS Code
git config --global core.editor "vim"          # Vim

# Colors
git config --global color.ui true

# View configuration
git config --list

Create repository

# Initialize local repository
git init

# Or clone existing
git clone https://github.com/user/repo.git

Basic workflow

1. Check status

# View repository status
git status

# View detailed changes
git diff

# View changes in specific files
git diff file.txt

2. Add changes

# Add specific file
git add file.txt

# Add all changes
git add .

# Add interactively (recommended)
git add -p

# See what was added
git status

3. Commit

# Commit with message
git commit -m "feat: add user authentication"

# Commit with description
git commit -m "feat: add authentication

- Implement login with email/password
- Add JWT token validation
- Create authentication middleware"

# Add and commit in one
git commit -am "fix: fix bug in price calculation"

4. View history

# View commits
git log

# View commits in one line
git log --oneline

# View commits with graph
git log --graph --oneline --all

# View changes in specific commit
git show <commit-hash>

Commit conventions

Use clear, descriptive commit messages. I recommend Conventional Commits:

# Format: type(scope): description

# Common types:
feat: new feature
fix: bug fix
docs: documentation
style: formatting (no code changes)
refactor: refactoring
test: tests
chore: maintenance tasks

# Examples:
git commit -m "feat(auth): add Google OAuth login"
git commit -m "fix(payment): fix tax calculation"
git commit -m "docs(readme): update installation instructions"
git commit -m "refactor(api): simplify validation logic"

Branches

Branches are essential for team work and organizing work.

Create and switch branches

# Create branch
git branch feature/new-feature

# Switch to branch
git checkout feature/new-feature

# Create and switch in one
git checkout -b feature/new-feature

# View branches
git branch

# View remote branches
git branch -r

# View all branches
git branch -a

Workflow with branches

# 1. Work on main/master (stable code only)
git checkout main

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

# 3. Make changes and commits
git add .
git commit -m "feat: implement login"

# 4. Update main
git checkout main
git pull origin main

# 5. Merge changes
git checkout feature/authentication
git merge main  # Or rebase (see below)

# 6. Push and create Pull Request
git push origin feature/authentication

Merge vs. Rebase

Merge

# Merge branch into main
git checkout main
git merge feature/new-feature

# Merge with message
git merge feature/new-feature -m "Merge feature/new-feature"

Advantages: Preserves full history Disadvantages: Can create complex history

Rebase

# Rebase branch onto main
git checkout feature/new-feature
git rebase main

# Interactive rebase (to clean up commits)
git rebase -i main

Advantages: Linear, clean history Disadvantages: Rewrites history (don’t use on shared branches)

When to use each

  • Merge: For shared branches, preserving full history
  • Rebase: For local branches, keeping history clean

Working with remotes

Add remote

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

# View remotes
git remote -v

# Change remote URL
git remote set-url origin https://github.com/user/new-repo.git

Push and Pull

# Push to remote
git push origin main

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

# Pull changes
git pull origin main

# Fetch (without merge)
git fetch origin

# View differences before pull
git fetch origin
git log HEAD..origin/main

Resolving conflicts

Conflicts are inevitable when working in a team.

When a conflict occurs

# Try merge/pull
git merge feature/other-branch

# Git will tell you:
# Auto-merging file.txt
# CONFLICT (content): Merge conflict in file.txt

Resolve conflict

# 1. View files with conflict
git status

# 2. Open file and look for markers:
<<<<<<< HEAD
code from your current branch
=======
code from the branch you're merging
>>>>>>> feature/other-branch

# 3. Edit file, remove markers, keep correct code

# 4. Mark as resolved
git add file.txt

# 5. Complete merge
git commit

Conflict tools

# Use merge tool
git mergetool

# Configure tool
git config --global merge.tool vimdiff
git config --global merge.tool code  # VS Code

Advanced commands

Stash - Save changes temporarily

# Save changes without commit
git stash

# Save with message
git stash save "WIP: work in progress"

# View stashes
git stash list

# Apply stash
git stash apply

# Apply and remove
git stash pop

# Delete stash
git stash drop

Reset - Undo changes

# Undo changes in working directory (keep staged)
git reset --soft HEAD~1

# Undo changes in staging (keep working directory)
git reset HEAD~1
git reset --mixed HEAD~1

# Undo everything (⚠️ WARNING: you lose changes)
git reset --hard HEAD~1

# Reset to specific commit
git reset --hard <commit-hash>

Revert - Revert commits

# Revert commit (creates new commit)
git revert <commit-hash>

# Revert last commit
git revert HEAD

Difference from reset: revert creates a new commit, reset removes commits.

Cherry-pick - Apply specific commit

# Apply commit from another branch
git cherry-pick <commit-hash>

# Apply multiple commits
git cherry-pick <commit1> <commit2>

Professional workflow

Git Flow

# Main branches:
# - main/master: production
# - develop: development
# - feature/*: new features
# - release/*: release preparation
# - hotfix/*: urgent fixes

# Create feature
git checkout develop
git pull origin develop
git checkout -b feature/new-feature

# Work and commit
git add .
git commit -m "feat: implement feature"

# Finish feature
git checkout develop
git merge feature/new-feature
git push origin develop

GitHub Flow (simpler)

# Branches:
# - main: production
# - feature/*: new features

# Create feature from main
git checkout main
git pull origin main
git checkout -b feature/new-feature

# Work and commit
git add .
git commit -m "feat: implement feature"
git push origin feature/new-feature

# Create Pull Request on GitHub/GitLab
# After approval, merge to main

Best practices

1. Small, frequent commits

# ❌ Bad: One huge commit
git commit -m "feat: add everything"

# ✅ Good: Small, specific commits
git commit -m "feat: add user model"
git commit -m "feat: add registration endpoint"
git commit -m "test: add registration tests"

2. Don’t commit generated files

# Create .gitignore
echo "node_modules/" >> .gitignore
echo ".env" >> .gitignore
echo "dist/" >> .gitignore
echo "*.log" >> .gitignore

3. Pull before Push

# Always update before push
git checkout main
git pull origin main
git checkout feature/new-feature
git merge main  # Or rebase
git push origin feature/new-feature

4. Review before committing

# See what will be committed
git status
git diff --staged

# View history before push
git log origin/main..HEAD

5. Use tags for releases

# Create tag
git tag v1.0.0

# Tag with message
git tag -a v1.0.0 -m "Release version 1.0.0"

# Push tags
git push origin v1.0.0
git push --tags

Useful commands

View differences

# Differences between branches
git diff main..feature/new-feature

# Differences in specific file
git diff main..feature/new-feature -- file.txt

# Change statistics
git diff --stat main..feature/new-feature

Search in history

# Search text in commits
git log -S "text" --source --all

# Search in commit messages
git log --grep="bug"

# Search by author
git log --author="Sebastian"

Clean repository

# View untracked files
git clean -n

# Remove untracked files
git clean -f

# Remove directories too
git clean -fd

This is a proven workflow that works well for most projects:

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

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

# 3. Work and make small commits
git add .
git commit -m "feat: step 1 of implementation"
git add .
git commit -m "feat: step 2 of implementation"

# 4. Push and create PR
git push -u origin feature/new-feature

# 5. After approval, merge
git checkout main
git pull origin main
git merge feature/new-feature
git push origin main

# 6. Delete local branch
git branch -d feature/new-feature

Common mistakes and how to avoid them

1. Commit on main/master

# If you accidentally committed on main:
git reset HEAD~1  # Undo commit (keep changes)
git checkout -b feature/new-feature
git add .
git commit -m "feat: new feature"

2. Force push on shared branch

# ❌ NEVER do this on shared branches:
git push --force origin main

# ✅ If you need to force (only on your branch):
git push --force-with-lease origin feature/my-branch

3. Lose changes

# Recover lost changes
git reflog  # View action history
git checkout <commit-hash>  # Go to specific commit
git checkout -b recovery-branch  # Create recovery branch

My personal perspective

Git isn’t optional. It’s essential. This workflow will help you:

  • Work safely and collaboratively
  • Never lose code
  • Resolve conflicts efficiently
  • Keep history clean and understandable

I’ve seen projects without version control that lost days or weeks of work. I’ve seen teams struggling with merge conflicts because they don’t follow a consistent workflow.

This workflow has been proven on projects of all sizes. It works for small and large teams, for simple and complex projects.

You don’t need to memorize all commands. Start with the basics (status, add, commit, push, pull) and gradually add more commands to your repertoire.

The key is to be consistent. Use the same workflow on all your projects. Over time, it will become second nature, and you’ll never lose code again.