Skip to content

Git & Version Control ​

Git tracks changes to your code over time. It lets you undo mistakes, experiment without breaking the working version, and collaborate with other people without stepping on each other's work. You do not need to master git to be productive. The handful of commands in this lesson cover what you will actually run on a real project, from starting something new to merging a finished feature.

What you'll learn

  • git init starts tracking a directory; git add stages changes; git commit saves a snapshot
  • git push and git pull move commits between your machine and a remote like GitHub
  • Branches isolate work in progress. Merge only when the feature is done and tested

The problem ​

You are working on a feature. It is going well, so you keep going. At some point you make a change that breaks something, but you have made ten other changes since then and you cannot remember which one caused the problem. You did not save a copy of the working version. You are stuck either debugging blindly or rewriting what you lost.

Version control solves this by saving snapshots of your code at every commit. You can rewind to any previous snapshot, compare what changed between two points, and isolate experimental work in branches until it is ready to merge.

Options & when to use each ​

ApproachWhat it isWhen to use it
Manual copies / project-final-v2-bak.zipSaving copies of the project directory with version namesNever. It is error-prone, uses disk space, and you cannot see what changed between copies
Git (local only)Track changes on your machine without a remoteSolo projects where you want undo and history but no collaboration or backup
Git + GitHub/GitLabLocal git plus a remote repository for backup and collaborationEverything else -- this is the default for the course

Build it ​

One-time setup ​

Tell git who you are. This information is attached to every commit:

bash
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
git config --global init.defaultBranch main

Starting a new project ​

bash
cd my-agent-project
git init
# Output: Initialized empty Git repository in /home/you/my-agent-project/.git/

A .git/ directory now exists. That is where git stores all its history and metadata. You never need to look inside it.

The .gitignore file ​

Before you commit anything, tell git which files to ignore. Create a .gitignore at the root of your project:

text
# Secrets and environment files
.env

# Python
__pycache__/
*.pyc
.venv/

# OS files
.DS_Store
Thumbs.db

# Build artifacts
dist/
*.egg-info/

The .gitignore prevents git from tracking generated files, secrets, and the virtual environment. Secrets in version control are a security problem. Generated files bloat the repository and cause merge conflicts.

The staging area -- git's mental model ​

Git has three zones. Understanding them prevents most confusion:

  1. Working directory: the files you edit in your editor. Git sees changes here but does nothing with them yet.
  2. Staging area (also called the index): files you have explicitly marked for the next commit. git add moves files here.
  3. Repository: the permanent history. git commit saves whatever is in the staging area.

A file can be modified in the working directory, staged, or committed. The same file can even be in different states across zones -- you can stage a change, then make another edit to the working copy before committing.

Your first commit ​

bash
# Stage everything that is not in .gitignore
git add .

# Save the staged changes as a commit
git commit -m "feat: initial project scaffold"

Write commit messages in present tense and be specific about what changed. "Fix bug" tells you nothing six months later. "Fix KeyError when config file is missing" tells you exactly what the commit addresses.

Pushing to GitHub ​

Create a repository on GitHub (the web UI, do not initialize with a README). Then connect your local repository:

bash
git remote add origin https://github.com/you/my-agent-project.git
git push -u origin main

After the first push, future pushes are shorter:

bash
git push

Pulling changes ​

When someone else (or you, from another machine) has pushed to the remote:

bash
git pull

This fetches new commits from the remote and merges them into your current branch.

Branching -- isolate work in progress ​

A branch is a parallel line of development. When you start a new feature, create a branch so your main branch stays stable while you experiment:

bash
# Create and switch to a new branch
git checkout -b feature/add-rate-limiting

# Work, stage, commit as usual
git add .
git commit -m "feat: add rate limiting to API client"

# Push the branch to GitHub
git push -u origin feature/add-rate-limiting

Switch between branches to work on different things:

bash
# List all branches, current one marked with *
git branch

# Switch to main
git checkout main

# Switch back to the feature branch
git checkout feature/add-rate-limiting

Merging ​

When the feature is done and tested, merge it back into main:

bash
git checkout main
git pull                    # Make sure main is up to date
git merge feature/add-rate-limiting
git push

If git cannot merge automatically because both branches changed the same lines, it will report a merge conflict. Open the conflicted file, look for the <<<<<<<, =======, and >>>>>>> markers, decide which version to keep, remove the markers, stage the file, and commit.

Cloning an existing project ​

When a repository already exists on GitHub and you want a local copy:

bash
git clone https://github.com/someone/some-project.git
cd some-project

Viewing history ​

bash
# Compact log, one commit per line
git log --oneline

# See what changed but is not yet staged
git diff

# See what is staged but not yet committed
git diff --staged

# See which files have changes
git status

What goes wrong ​

MistakeHow you notice itThe fix
Committed secrets (.env, API keys).env appears in git log or the remote repositoryRotate the secret immediately -- removing it from git does not undo exposure. Then git rm --cached .env, add .env to .gitignore, commit, push
Committed large generated filesRepository size balloons, clone takes foreverAdd the file pattern to .gitignore, git rm --cached the files, commit. Consider git filter-branch or BFG Repo-Cleaner for deep history cleanup
Detached HEAD stategit status says "HEAD detached at ..."You are not on a branch. git checkout -b temp-branch to save your work to a branch, then switch back to main
Merge conflictGit prints "CONFLICT" and the merge abortsOpen each conflicted file, resolve the markers, git add the resolved file, git commit to complete the merge
Pushed to the wrong branchChanges appear in a branch you did not intendIf nobody has pulled yet: git push --force on the wrong branch to revert, then push to the correct branch. If people have pulled: coordinate, do not force-push
Forgot to pull before workinggit push rejects because remote has commits you do not have locallygit pull first, resolve any conflicts, then push

Confirm it worked ​

After setting up git and making your first commit, verify everything is tracked correctly:

bash
# 1. git status should show a clean working directory
git status
# Expected: "nothing to commit, working tree clean"

# 2. git log should show your commit
git log --oneline
# Expected: one line with your commit message and a hash

# 3. git remote should list origin
git remote -v
# Expected: origin  https://github.com/you/my-agent-project.git (fetch)
# Expected: origin  https://github.com/you/my-agent-project.git (push)

# 4. Verify .gitignore is working
echo "test" > .env
git status
# Expected: .env does NOT appear in the untracked files list
rm .env

Next: PostgreSQL Basics