Ad – 728×90
🎯 Interview Prep

Git Practical Exercises – Hands-On Git Challenges

Reading about Git only gets you so far. True fluency comes from muscle memory — running commands, hitting errors, figuring out why. These 15 exercises are designed to build real-world Git skills from the ground up. Work through them in order: each exercise builds on skills from the previous ones. All exercises can be done locally with nothing but Git installed.

⏱️ 60 min practice 🎯 All Levels 📅 Updated 2026

Beginner Exercises

💡
Getting started

Create a dedicated practice directory for all exercises: mkdir ~/git-practice && cd ~/git-practice. Each exercise will have its own subdirectory so they don't interfere with each other.

Exercise 1: Your First Repository

Scenario: Start a new project from scratch, make three commits, and explore the history.

  1. Create a new directory and initialize a Git repo: mkdir ex1-first-repo && cd ex1-first-repo && git init
  2. Create README.md with "# My First Git Project" and commit it: git add README.md && git commit -m "docs: add README"
  3. Create index.html with a basic HTML skeleton. Stage and commit: git commit -m "feat: add index.html"
  4. Add a <h1> tag to index.html. Stage and commit: git commit -m "feat: add page heading"
  5. View the history: git log --oneline — you should see 3 commits
  6. View the full details of the second commit: git show HEAD~1
ℹ️
Expected outcome: git log --oneline shows 3 commits. git show HEAD~1 shows the diff for the index.html commit.

Exercise 2: Branching, Switching, and Merging

Scenario: Add a feature on a branch, then merge it back to main.

  1. From your repo in ex1, check you're on main: git branch
  2. Create and switch to a new branch: git switch -c feature/add-nav
  3. Create nav.html with navigation links. Commit it on the feature branch.
  4. Add a link to nav.html in index.html. Make another commit on the feature branch.
  5. Switch back to main: git switch main
  6. Merge the feature branch: git merge feature/add-nav
  7. View the graph: git log --oneline --graph
  8. Delete the feature branch: git branch -d feature/add-nav
ℹ️
Expected outcome: All commits are on main. The log shows a linear or merge-committed history. The feature branch is gone from git branch.
Ad – 336×280

Exercise 3: Partial Staging with git add -p

Scenario: You've made two unrelated changes in one file. Commit them as separate, focused commits.

  1. Open index.html and make two separate changes: add a <footer> section near the bottom AND fix a typo at the top
  2. Run git diff to see both changes
  3. Run git add -p index.html to enter interactive staging
  4. Stage only the typo fix (press y for the first hunk, n for the footer hunk)
  5. Commit just the typo: git commit -m "fix: correct heading typo"
  6. Now stage and commit the footer: git add index.html && git commit -m "feat: add footer section"
  7. Verify with git log --oneline — two separate commits
ℹ️
Hint: If the changes are in the same "hunk" (too close together in the file), press s to split the hunk into smaller pieces.

Exercise 4: Discard Changes with git restore

Scenario: You've made changes you don't want to keep.

  1. Make some experimental changes to index.html (don't stage them)
  2. Verify with git diff that changes are there
  3. Discard all unstaged changes: git restore index.html
  4. Verify with git diff — output should be empty now
  5. Now stage a change: git add index.html
  6. Unstage it: git restore --staged index.html
  7. Verify it's now unstaged: git status
ℹ️
Hint: git restore (without --staged) discards working directory changes — this cannot be undone! Make sure you really don't want those changes.

Exercise 5: Set Up SSH Key for GitHub

Scenario: Configure passwordless authentication with GitHub using SSH.

  1. Check for existing SSH keys: ls ~/.ssh/*.pub
  2. If none exist, generate a new key: ssh-keygen -t ed25519 -C "your-email@example.com"
  3. Start the SSH agent: eval "$(ssh-agent -s)"
  4. Add your key to the agent: ssh-add ~/.ssh/id_ed25519
  5. Copy your public key: cat ~/.ssh/id_ed25519.pub
  6. On GitHub: Settings → SSH and GPG keys → New SSH key → paste the key
  7. Test the connection: ssh -T git@github.com — should say "Hi username! You've successfully authenticated"

Intermediate Exercises

Exercise 6: Create a Branch from a Past Commit

Scenario: A bug was introduced 3 commits ago. Create a branch from the "last good" commit.

  1. Use git log --oneline to identify a commit from 3 commits ago and copy its hash
  2. Create a branch starting at that commit: git switch -c bugfix/from-past <commit-hash>
  3. Verify you're on the right commit: git log --oneline -3
  4. Make a fix commit on this branch
  5. View how your branch diverges: git log --oneline --graph --all
ℹ️
Hint: You can also reference past commits relatively: git switch -c bugfix/from-past HEAD~3 means "3 commits before HEAD".

Exercise 7: Simulate and Resolve a Merge Conflict

Scenario: Two branches modify the same line — create and resolve the conflict.

  1. Create a new repo with one commit (a file with "Hello World" on line 1)
  2. Create branch A: change "Hello World" to "Hello from Branch A" and commit
  3. Switch back to main. Create branch B: change "Hello World" to "Hello from Branch B" and commit
  4. Merge branch A into main: git merge feature-a
  5. Now merge branch B: git merge feature-b — this will conflict
  6. Open the conflicted file, see the conflict markers. Choose "Hello from Branch A and B!" as the resolution
  7. Remove conflict markers, save, then: git add . && git commit
  8. View the final graph: git log --oneline --graph --all

Exercise 8: Interactive Rebase – Squash 3 Commits

Scenario: Your feature branch has 3 WIP commits. Squash them into one clean commit before merging.

  1. Create a new branch and make 3 commits with messages: "WIP", "WIP: more progress", "WIP: done"
  2. Run interactive rebase on the last 3 commits: git rebase -i HEAD~3
  3. In the editor, change the second and third lines from pick to squash (or s)
  4. Save and close. A new editor opens for the combined commit message — write: "feat: add complete feature"
  5. Verify with git log --oneline — 3 commits are now 1
ℹ️
Hint: Interactive rebase opens your default text editor. If you're unfamiliar with Vim, set a different editor first: git config --global core.editor "nano" or git config --global core.editor "code --wait" for VS Code.

Exercise 9: Cherry-Pick a Commit from Another Branch

Scenario: A useful commit was made on a feature branch — bring it to main without merging the whole branch.

  1. Create a branch feature/experiments and make 3 commits (add features A, B, and C)
  2. Switch back to main: git switch main
  3. View the feature branch commits: git log --oneline feature/experiments
  4. Copy the hash of the "feature B" commit (the middle one)
  5. Cherry-pick just that commit: git cherry-pick <hash>
  6. Verify main has "feature B" but not A or C: git log --oneline

Exercise 10: Create an Annotated Tag

Scenario: Mark your project's first release with an annotated tag and push it.

  1. Make sure you have at least 3 commits on main
  2. Create an annotated tag: git tag -a v1.0.0 -m "Release v1.0.0 – first stable release"
  3. View tag details: git show v1.0.0
  4. List all tags: git tag
  5. If you have a GitHub repo: push the tag: git push origin v1.0.0
  6. Verify the tag appears on GitHub under Releases → Tags

Advanced Exercises

Exercise 11: Use git bisect to Find a Bug

Scenario: A project has 10 commits, and one of them introduced a bug. Find it with bisect.

  1. Create a repo and make 10 commits. In commit 6 (not the last one), introduce a "bug": add a line const BUG = true; to a file
  2. Start bisect: git bisect start
  3. Mark current HEAD as bad: git bisect bad
  4. Mark the first commit as good: git bisect good <first-commit-hash>
  5. For each checkout, check if the file contains "BUG = true". Mark good or bad accordingly.
  6. Continue until Git identifies the first bad commit
  7. Verify it found commit 6
  8. Reset: git bisect reset
ℹ️
Hint: Use grep -r "BUG = true" . at each checkout to test for the bug. With 10 commits, bisect will find it in at most 4 steps.

Exercise 12: Write a pre-commit Hook That Checks for console.log

Scenario: Prevent console.log statements from being committed to the repository.

  1. Create a new repo with a src/ directory and a JavaScript file
  2. Create the pre-commit hook: touch .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
  3. Write the hook script to check staged JS files for console.log:
Bash (.git/hooks/pre-commit)
#!/bin/sh
STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep "\.js$")
if [ -n "$STAGED" ]; then
  if git diff --cached "$STAGED" | grep "^+.*console\.log"; then
    echo "Error: Remove console.log before committing"
    exit 1
  fi
fi
exit 0
  1. Add console.log("debug") to a JS file and try to commit — it should be rejected
  2. Remove the console.log and commit again — it should succeed

Exercise 13: Set Up Conventional Commits with commitlint

Scenario: Enforce Conventional Commits format on every commit in a Node.js project.

  1. Create a Node.js project: npm init -y
  2. Install commitlint and Husky: npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
  3. Create commitlint config: echo "module.exports = {extends:['@commitlint/config-conventional']}" > commitlint.config.js
  4. Initialize Husky: npx husky init
  5. Add commit-msg hook: echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
  6. Test with a bad commit: git commit -m "changed stuff" — should fail
  7. Test with a good commit: git commit -m "feat: add initial setup" — should pass

Exercise 14: Fork a Repo, Sync Upstream, Open a PR

Scenario: Practice the open-source contribution workflow with a real GitHub fork.

  1. On GitHub, fork any public repository (e.g., a documentation repo or a simple tool)
  2. Clone your fork: git clone git@github.com:YOUR-USERNAME/repo-name.git
  3. Add the original as upstream: git remote add upstream git@github.com:ORIGINAL-OWNER/repo-name.git
  4. Verify remotes: git remote -v — should show both origin and upstream
  5. Create a feature branch: git switch -c fix/typo-in-readme
  6. Make a small change (fix a typo, improve a sentence)
  7. Push to your fork: git push -u origin fix/typo-in-readme
  8. Open a PR from your fork to the original repo on GitHub

Exercise 15: Simulate a Git Flow Release Cycle

Scenario: Practice the full Git Flow branching model for one complete release.

  1. Create a repo with main and develop branches: git switch -c develop
  2. Create and complete two feature branches: feature/login and feature/dashboard — both merge back to develop
  3. Create a release branch: git switch -c release/1.0.0 develop
  4. Make a minor fix on the release branch (e.g., update version number in package.json)
  5. Merge release to main: git switch main && git merge --no-ff release/1.0.0
  6. Tag the release: git tag -a v1.0.0 -m "Release v1.0.0"
  7. Merge release back to develop: git switch develop && git merge --no-ff release/1.0.0
  8. Delete the release branch
  9. View the full history: git log --oneline --graph --all
ℹ️
Expected outcome: The graph shows the complete Git Flow diamond pattern — features branching from develop, a release branch, and the final merge to main with a version tag.

📋 Summary

  • Beginner: Init repo → commit → branch → merge → partial staging → discard changes → SSH setup.
  • Intermediate: Branch from past commit → resolve conflicts → interactive rebase/squash → cherry-pick → annotated tags.
  • Advanced: git bisect → pre-commit hooks → commitlint + Husky → fork/PR workflow → full Git Flow cycle.
  • The best way to learn Git is by doing — run every exercise in a real terminal.
  • Make mistakes on purpose: try to break things, then figure out how to fix them using reflog.

FAQ

I messed up an exercise and my repo is in a bad state. How do I start over? +

The easiest fix: delete the exercise directory and start fresh — cd .. && rm -rf ex1-first-repo && mkdir ex1-first-repo && cd ex1-first-repo && git init. For practice repos, this is completely fine. For recovery without starting over: git reflog shows every recent HEAD position — find the state you want to return to and run git reset --hard <reflog-hash>. If things are really confused, git status usually gives you hints about what state you're in and what command to run.

Do I need a GitHub account for these exercises? +

For most exercises (1–12), no — everything is local. GitHub is only needed for Exercise 5 (SSH key setup), Exercise 10 (pushing a tag), Exercise 14 (fork and PR workflow), and optionally Exercise 15. If you don't have a GitHub account, you can simulate the remote using a second local clone: git clone --bare /path/to/repo /path/to/bare-remote.git and use that as your "remote" for push/pull exercises.

How do I practice git bisect if I don't have a repo with 10 commits? +

Create one quickly with a shell script: for i in $(seq 1 10); do echo "change $i" >> file.txt; git add .; git commit -m "commit $i"; done. Then use a text editor to add the "bug" marker to the file at a specific commit using interactive rebase (git rebase -i HEAD~5 and use the edit command to stop at commit 6, add the bug text, git add . && git commit --amend --no-edit, then git rebase --continue).