Appendix F: Version Control Systems#

What is Git?#

Git is a distributed version control system that:

  • Tracks changes — Every edit is recorded with who made it and when

  • Enables collaboration — Multiple developers work on the same project

  • Preserves history — You can revert to any previous version

  • Branches — Develop features independently without affecting main code

Centralized vs Distributed#

Centralized (SVN):

Developer ←→ Central Server
  • Single source of truth

  • Requires server access

  • No offline history

Distributed (Git):

Developer A's Repo ←→ GitHub ←→ Developer B's Repo
  • Full history on each machine

  • Works offline

  • More resilient (multiple backups)

Installing and Configuring Git#

Installation#

macOS (using Homebrew):

$ brew install git

Ubuntu/Debian:

$ sudo apt-get install git

Windows: Download from git-scm.com

Verify Installation#

$ git --version
git version 2.39.2

Configure Git#

Set your identity (used in all commits):

$ git config --global user.name "Your Name"
$ git config --global user.email "you@example.com"

# Verify
$ git config --global user.name
Your Name

# View all settings
$ git config --global --list

Optional: Configure Default Editor#

# Use vim
$ git config --global core.editor vim

# Use nano
$ git config --global core.editor nano

Git Fundamentals#

The Three Stages#

┌──────────────┐      ┌─────────────┐      ┌──────────────┐
│  Working Dir │  add │   Staging   │ commit│   Repository │
│  (modified)  │─────→│  (staged)   │──────→│  (committed) │
└──────────────┘      └─────────────┘      └──────────────┘
  1. Working Directory — Your local files (modified but not tracked)

  2. Staging Area — Files marked for commit

  3. Repository — Committed history (.git folder)

Initialize a Repository#

# Create a new project
$ mkdir myproject
$ cd myproject

# Initialize git
$ git init
Initialized empty Git repository in /Users/user/myproject/.git/

# Verify
$ ls -la
total 0
drwxr-xr-x  3 user  group   96 Jan 15 myproject
drwxr-xr-x  9 user  group  288 Jan 15 .git

Check Repository Status#

# Create a file
$ echo "Hello" > README.md

# Check status
$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        README.md

nothing added to commit but untracked files present (tracking)

Basic Git Workflow#

1. Stage Files#

# Stage a specific file
$ git add README.md

# Stage all changes
$ git add .

# Stage with pattern
$ git add *.py

# Check staging area
$ git status
On branch main

Initial commit

Changes to be committed:
  (use "rm --cached <file>..." to unstage)
        new file:   README.md

2. Commit Changes#

# Commit with message
$ git commit -m "Add README"
[main (root-commit) a1b2c3d] Add README
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

# Commit with detailed message
$ git commit -m "Add README" -m "Includes project description and setup instructions"

# Skip staging, commit all tracked files
$ git commit -am "Update documentation"

3. View History#

# View commits
$ git log
commit a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Author: Your Name <you@example.com>
Date:   Mon Jan 15 14:30:00 2024 -0500

    Add README

# Condensed view
$ git log --oneline
a1b2c3d Add README

# See what changed in each commit
$ git log -p

# Limit commits
$ git log -5      # Last 5 commits
$ git log --since="2 weeks ago"

4. View Differences#

# Changes in working directory (not staged)
$ git diff

# Changes in staging area
$ git diff --staged

# Differences between commits
$ git diff a1b2c3d..e5f6g7h

# Changes to specific file
$ git diff README.md

Branching and Merging#

Branches allow parallel development without affecting the main codebase.

Understanding Branches#

main:    o———o———o (stable, production-ready)
         \   \\
 feature:  o———o———o (new feature under development)

Branch Operations#

# List branches
$ git branch
* main

# Create a new branch
$ git branch feature/login

# Switch to branch
$ git checkout feature/login
Switched to branch 'feature/login'

# Shortcut: create and switch
$ git checkout -b feature/login
Switched to a new branch 'feature/login'

# Make changes and commit
$ echo "login code" > login.py
$ git add login.py
$ git commit -m "Add login functionality"

# Switch back to main
$ git checkout main
Switched to branch 'main'

Merging Branches#

# Merge feature into main
$ git checkout main
$ git merge feature/login
Updating a1b2c3d..e5f6g7h
Fast-forward
 login.py | 1 +
 1 file changed, 1 insertion(+)

# Delete feature branch (cleanup)
$ git branch -d feature/login
Deleted branch feature/login

Merge Conflicts#

When the same file is edited in different branches:

$ git merge feature/new-ui
Auto-merging app.py
CONFLICT (content): Merge conflict in app.py

# View conflicted file
$ cat app.py
<<<<<<< HEAD
# Main branch version
print("version 1")
=======
# Feature branch version
print("version 2")
>>>>>>> feature/new-ui

# Edit file to resolve
$ echo 'print("version 1 + feature")' > app.py

# Stage and commit
$ git add app.py
$ git commit -m "Resolve merge conflict"

Working with Remote Repositories#

Remote repositories (on GitHub, GitLab, etc.) enable collaboration and backup.

Adding a Remote#

# Add remote (after creating repo on GitHub)
$ git remote add origin https://github.com/username/myproject.git

# Verify
$ git remote -v
origin  https://github.com/username/myproject.git (fetch)
origin  https://github.com/username/myproject.git (push)

Pushing to Remote#

# Push main branch
$ git push -u origin main
Branch 'main' set up to track remote branch 'main' from 'origin'.

# After setup, just use git push
$ git push

# Push specific branch
$ git push origin feature/login

Pulling from Remote#

# Get changes from remote
$ git pull
remote: Enumerating objects: 3, done.
Updating a1b2c3d..e5f6g7h
Fast-forward
 file.py | 2 +-
 1 file changed, 1 insertion(+)

# Equivalent to:
$ git fetch         # Download changes
$ git merge origin/main  # Apply changes

Cloning a Repository#

# Clone existing project
$ git clone https://github.com/username/project.git
Cloning into 'project'...

# Navigate to it
$ cd project

# Verify remote is set up
$ git remote -v
origin  https://github.com/username/project.git (fetch)
origin  https://github.com/username/project.git (push)

GitHub Basics#

GitHub is a platform for hosting Git repositories and collaborating.

Creating a Repository on GitHub#

  1. Go to github.com and sign in

  2. Click NewNew repository

  3. Fill in:

    • Repository name: myproject

    • Description: (optional)

    • Public/Private: Choose visibility

    • Initialize with README: Optional (skip if you have local repo)

  4. Click Create repository

Push Existing Project to GitHub#

# From local project directory
$ git remote add origin https://github.com/username/myproject.git
$ git branch -M main
$ git push -u origin main

GitHub Collaboration#

# Clone a project
$ git clone https://github.com/otheruser/project.git
$ cd project

# Create feature branch
$ git checkout -b feature/my-addition

# Make changes
$ echo "my code" > feature.py
$ git add feature.py
$ git commit -m "Add my feature"

# Push to your fork (if contributing to others' projects)
$ git push origin feature/my-addition

Then create a Pull Request on GitHub to propose changes.

Reading GitHub Project#

# Check out others' branches to test
$ git fetch origin
$ git checkout origin/feature/interesting

# Follow a project without cloning
$ git remote add upstream https://github.com/originalauthor/project.git
$ git fetch upstream    # Get their updates

Common Tasks and Tricks#

Undoing Changes#

# Discard changes in working directory
$ git checkout -- file.py

# Unstage file
$ git reset HEAD file.py

# Undo last commit (keep changes)
$ git reset --soft HEAD~1

# Undo last commit (discard changes)
$ git reset --hard HEAD~1

# Revert a commit (create new commit that undoes it)
$ git revert a1b2c3d

Viewing File History#

# Who changed each line
$ git blame file.py

# History of a file
$ git log file.py

# Show specific commit
$ git show a1b2c3d

Stashing Work#

Save work temporarily without committing:

# Save current changes
$ git stash
Saved working directory and index state WIP on main: a1b2c3d Add README

# Switch branches
$ git checkout feature/urgent

# Return to saved work
$ git checkout main
$ git stash pop

Searching History#

# Find commit by message
$ git log --grep="bug fix"

# Find commits that changed a specific line
$ git log -S"function_name" -p

# Find who deleted something
$ git log -S"deleted_text"

Creating Aliases#

# Shorter commands
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
$ git config --global alias.unstage 'reset HEAD --'

# Now use:
$ git co main
$ git ci -m "message"

Best Practices#

Commit Messages#

Good commit messages are:

  • Concise: First line ≤ 50 characters

  • Imperative: “Add feature” not “Added feature”

  • Descriptive: Explain why, not just what

# Good
$ git commit -m "Fix login form validation"
$ git commit -m "Refactor database connection handling"
$ git commit -m "Update dependencies to address security CVE-2024-xxx"

# Bad
$ git commit -m "fixed stuff"
$ git commit -m "asdf"
$ git commit -m "work in progress"

Branching Strategy#

main (always deployable)
  ↓
release/v1.0 (for fixes before release)
  ↓
develop (integration branch)
  ↓
feature/login, feature/signup, bugfix/404 (feature branches)

Workflow:

  1. Create feature branch from develop

  2. Make changes and commit

  3. Push to GitHub

  4. Create Pull Request (PR)

  5. Review and merge to develop

  6. When ready, merge developmain

Before Pushing#

# Verify you're on the right branch
$ git status

# Review your changes
$ git diff

# Check log
$ git log -1

# Run tests (if available)
$ npm test  # or pytest, etc.

# Then push
$ git push

Keep Local Updated#

# Fetch changes from remote
$ git fetch origin

# Rebase your work on top of latest
$ git rebase origin/main

# Or merge
$ git merge origin/main

Troubleshooting#

“Detached HEAD” State#

# You checked out a commit instead of a branch
$ git checkout a1b2c3d
You are in 'detached HEAD' state

# Fix: Go back to a branch
$ git checkout main

“File already exists in the index”#

# You added a file that already exists
$ git add file.txt
error: The following files have matches:
        file.txt

# Use --force or --ignore-all-space
$ git add -f file.txt

“Fatal: refusing to merge unrelated histories”#

# Happens when cloning a repo with different history
$ git merge --allow-unrelated-histories origin/main

Lost Commits#

# View all commits ever made (even "lost" ones)
$ git reflog
a1b2c3d HEAD@{0}: reset: moving to HEAD~1
e5f6g7h HEAD@{1}: commit: Latest work

# Recover the commit
$ git reset --hard e5f6g7h

Need to Check Something in Old Code?#

# Create temporary branch from old commit
$ git checkout -b temp-check a1b2c3d

# Look around
$ cat old-version-file.py

# Go back
$ git checkout main
$ git branch -d temp-check

Quick Reference#

Essential Commands#

Task

Command

Setup

git config --global user.name "Name"

Initialize

git init

Status

git status

Stage

git add file.txt

Commit

git commit -m "message"

History

git log

Create branch

git checkout -b feature

Switch branch

git checkout main

Merge

git merge feature

Push

git push origin main

Pull

git pull

Clone

git clone URL

Where to Learn More#

Practice#

Create a test repository and practice:

$ mkdir git-practice
$ cd git-practice
$ git init
$ echo "test" > file.txt
$ git add file.txt
$ git commit -m "Initial commit"
$ git branch feature
$ git checkout feature
$ echo "feature work" >> file.txt
$ git commit -am "Add feature"
$ git checkout main
$ git merge feature