Exercises: Version Control with Git
These exercises progress from basic Git commands to realistic collaboration scenarios. You'll need a terminal with Git installed for all exercises. Create a fresh test directory for each exercise group to avoid confusion.
Difficulty Guide: - ⭐ Foundational (5-10 min each) - ⭐⭐ Intermediate (10-20 min each) - ⭐⭐⭐ Challenging (20-40 min each) - ⭐⭐⭐⭐ Advanced/Research (40+ min each)
Part A: Repository Basics ⭐
A.1. Create a new directory called git-practice. Initialize it as a Git repository. Verify the .git directory was created. Run git status and explain the output.
A.2. Inside git-practice, create a Python file called hello.py that prints "Hello, Git!". Run git status. What does Git say about the file? What state is it in?
A.3. Stage hello.py with git add, then run git status again. What changed in the output? Now commit with the message "Add hello script". Run git log to verify your commit.
A.4. Modify hello.py to also print "This is my first tracked file." Run git diff to see the changes. Then stage and commit with an appropriate message.
A.5. Run git log to see your two commits. Identify the following for each commit: the commit hash, the author, the date, and the message. How are the two commits related?
A.6. Create two more files: goodbye.py (prints a farewell message) and greet.py (prints a greeting that uses the user's name from input()). Stage and commit both files in a single commit with a message that describes both additions.
Part B: The Staging Area ⭐⭐
B.1. Modify both hello.py and goodbye.py in your git-practice repo. Now stage only hello.py and commit it. Then stage and commit goodbye.py separately. Run git log --oneline to verify you have two separate commits.
B.2. Create a file called notes.txt with some temporary notes. Also create a file called config.py with a database password hardcoded (use a fake one). Now stage only notes.txt using git add notes.txt. Run git status and explain the difference between the staged file and the unstaged file. (Do NOT commit config.py — it contains a secret.)
B.3. Create three Python files: module_a.py, module_b.py, and module_c.py, each containing a simple function. Practice the following staging scenarios:
- Stage all three at once using git add .
- Unstage module_c.py using git restore --staged module_c.py
- Commit only module_a.py and module_b.py
- Then commit module_c.py separately
B.4. Make changes to an already-committed file. Run git diff to see the unstaged changes. Then run git add on the file and run git diff --staged to see the staged changes. What's the difference between git diff and git diff --staged?
Part C: Branching and Merging ⭐⭐
C.1. In your git-practice repo, create a branch called feature-countdown. Switch to it. Write a Python file countdown.py that counts down from 10 to 1 and prints "Liftoff!". Commit it. Switch back to main. Verify that countdown.py doesn't exist on main. Then merge feature-countdown into main.
C.2. Create a branch called experiment. On this branch, modify hello.py to print in ALL CAPS. Commit the change. Now switch back to main and modify hello.py to print in lowercase. Commit that change too. Now try to merge experiment into main. You should get a merge conflict. Resolve it by choosing whichever version you prefer, then complete the merge.
C.3. Create the following branch structure, making at least one commit on each branch:
- main (your starting point)
- feature-a (branched from main)
- feature-b (branched from main)
Merge feature-a into main first, then merge feature-b. Did either merge produce a conflict? Why or why not?
C.4. Run git log --oneline --graph --all after completing C.3. Draw (on paper or in a text file) what the branch history looks like. Label each commit with its message.
Part D: .gitignore ⭐⭐
D.1. In a new Git repository, create the following files:
- app.py (a Python script)
- config.env (a fake config file with API_KEY=abc123)
- notes.txt (personal notes)
- A __pycache__/ directory with a dummy .pyc file inside it
Write a .gitignore that excludes config.env, notes.txt, and __pycache__/. Run git status to verify these files no longer appear. Commit app.py and .gitignore.
D.2. Suppose you already committed secrets.py to your repo before creating a .gitignore. Adding secrets.py to .gitignore won't remove it from tracking. Research and use git rm --cached secrets.py to stop tracking it without deleting the file from disk. Verify with git status that the file is now untracked.
D.3. Write a .gitignore appropriate for a Python project that uses:
- A virtual environment in venv/
- pytest for testing
- VS Code as the editor
- A .env file for environment variables
- Jupyter notebooks (you want to track .ipynb files but not .ipynb_checkpoints/)
Test it by creating dummy files/directories matching each pattern and verifying git status ignores them.
Part E: Workflow Scenarios ⭐⭐⭐
E.1. (Solo Developer Workflow) Simulate a full solo development session:
1. Create a new repo with a Python project containing at least three files
2. Make an initial commit
3. Create a feature branch, implement a feature across two files, commit
4. Switch to main, create a different branch, make a different change, commit
5. Merge both branches into main
6. Write a .gitignore and commit it
7. Run git log --oneline --graph --all and screenshot or copy the output
E.2. (Simulating Collaboration) Even without GitHub, you can simulate collaboration using two separate clones. In a directory, create a "bare" repository (git init --bare shared.git). Clone it twice into developer_a/ and developer_b/. Have developer A create and push a file, then have developer B pull it. Then have both developers modify the same file and practice resolving the merge conflict.
E.3. (The Code Review) Pick any Python file you wrote in a previous chapter (from TaskFlow or an exercise). Review it through the lens of version control:
- If you were committing this file for the first time, what would your commit message say?
- If someone asked you to add a feature, what branch name would you use?
- What files in the project should be in .gitignore?
- Write three commits (with messages) that would incrementally improve the code
E.4. (Recovering from Mistakes) Practice the following recovery scenarios:
1. Make a commit, then use git log to find its hash. Use git show <hash> to inspect it.
2. Modify a file, then discard the changes with git restore <file>.
3. Stage a file, then unstage it with git restore --staged <file>.
4. Make a commit with a typo in the message. Fix it with git commit --amend -m "corrected message" (only safe for commits you haven't pushed).
Part F: Applied Challenges ⭐⭐⭐⭐
F.1. (Full Project Setup) Take a Python project from a previous chapter exercise (at least 3 files). Set it up as a proper Git repository from scratch:
- Initialize Git
- Create a comprehensive .gitignore
- Write a README.md with project description, installation, usage, and structure
- Make a series of at least 5 commits that build up the project incrementally (don't commit everything at once — simulate realistic development)
- Use at least two feature branches
- Show the final git log --oneline --graph
F.2. (Open Source Contribution Simulation) Go to a beginner-friendly open-source repository on GitHub (search for repos with the "good first issue" label). Without actually submitting anything:
- Fork the repo
- Clone your fork
- Create a branch
- Read the project's CONTRIBUTING.md (if it has one)
- Write a summary of what you'd need to do to submit a pull request to that project
F.3. (Git Detective) Clone a well-known open-source Python project (e.g., requests, flask, or httpx). Use git log, git log --oneline, git log --author, and git log --since to answer these questions:
- How many commits does the project have?
- Who are the top 3 contributors by commit count?
- What was the most recent commit message?
- How many commits were made in the last 30 days?
F.4. (Commit Message Audit) Review the last 20 commits of a popular open-source project on GitHub. Categorize each commit message as "good" (specific, verb-first, clear) or "could improve" (vague, too long, unclear). Calculate the percentage of good messages. Write a brief analysis of the project's commit message conventions.