β³ Git & GitHub: Your Code's Time Machine
Git isn't just a save button β it's a time machine with parallel universes. You can spin off a branch to try a risky idea, keep your working code untouched, and either merge the experiment in or throw it away with zero regret. This tutorial covers branching, merging, collaborating through pull requests, and β when things go wrong β traveling backward through your history to undo almost anything.
Reference & Extra Tutorials · Resources · Git Survival Kit
π― What This Covers
By the end of this reference, you will be able to:
- Explain what a branch is and why parallel lines of work make development safer
- Create and move between branches with
git switchandgit switch -c - Merge a finished branch back into
mainand understand fast-forward vs merge commits - Collaborate through the GitHub fork β branch β pull request workflow
- Undo mistakes with the right tool:
restore,revert, orreset - Recover "lost" work using
git reflogwhen a command seems to have eaten it
Estimated Time: 50 minutes
Practice: Build a feature on a branch, merge it, then travel back and undo a bad commit.
In This Reference
Why Branches Exist
A branch is an independent line of development β a parallel universe for your code. The main branch holds your known-good, working project. When you want to build a feature or try a risky refactor, you create a branch off main, work freely there, and only fold it back in once it's ready. If the experiment fails, you delete the branch and main was never affected.
Picture writing a novel and wanting to try a completely different ending. Instead of overwriting your real manuscript, you photocopy the last chapter and scribble on the copy. If the new ending is great, you staple it in; if not, you toss the copy. Your original was never at risk. That's a branch.
main, gains its own commits, and merges back once finished β main stays stable the whole time.π‘ Branches are cheap and fast
Creating a branch in Git is nearly instant and costs almost nothing β under the hood it's just a movable pointer to a commit. That's why professional teams branch constantly: one branch per feature, per bug fix, per experiment. Branching liberally is a sign you're using Git well.
Creating & Switching Branches
Modern Git uses git switch to move between branches and git switch -c to create-and-move in one step. (You'll see the older git checkout and git checkout -b everywhere too β they still work and do the same thing, but switch is clearer because it does only one job.)
# See your branches β the current one is marked with *
git branch
# Create a new branch AND switch to it
git switch -c feature/search-recipes
# Move between existing branches
git switch main
git switch feature/search-recipes
# The older, equivalent syntax
git checkout -b feature/search-recipes # create + switch
git checkout main # switch
What "switching" means: Git rewrites the files in your working directory to match the branch you moved to. Switch to main and your search feature vanishes from the folder; switch back and it reappears. Nothing is lost β each branch remembers its own state.
β οΈ Commit or stash before switching
If you have uncommitted changes that would conflict with the target branch, Git refuses to switch to avoid losing them. Either commit your work first, or tuck it away temporarily with git stash (and bring it back later with git stash pop).
Naming branches clearly
Teams use prefixes so a branch's purpose is obvious at a glance:
| Pattern | Example | Meaning |
|---|---|---|
feature/ | feature/user-login | A new capability |
fix/ | fix/cart-total-rounding | A bug fix |
docs/ | docs/api-readme | Documentation only |
Merging Branches Back
Once a branch's work is done and tested, you merge it into main to make it part of the real project. You switch to the branch that should receive the changes (main), then merge the feature branch into it.
# Move to the receiving branch
git switch main
# Bring the feature branch's commits in
git merge feature/search-recipes
# Clean up the finished branch
git branch -d feature/search-recipes
π Fast-forward vs merge commit
If main hasn't changed since you branched, Git simply slides main's pointer forward to include your commits β a fast-forward, leaving a straight-line history. If main gained its own commits meanwhile, Git creates a merge commit that ties the two histories together. Both are normal; the second just records that two lines of work came together.
Here's the full lifecycle of a feature, from branch to merged and deployed:
β οΈ Merge conflicts happen β and that's fine
If both branches changed the same lines, Git pauses and asks you to choose, inserting <<<<<<< / ======= / >>>>>>> markers around the disputed code. Edit the file to the version you want, delete the markers, then git add and git commit. To bail out entirely, git merge --abort returns you to exactly where you started.
Collaborating with Pull Requests
On a team, you rarely merge straight into main yourself. Instead you push your branch to GitHub and open a pull request (PR) β a proposal that says "please review these changes and merge them." Teammates comment, request tweaks, and approve; automated tests run; then the PR is merged through GitHub's interface.
# Do your work on a branch
git switch -c feature/newsletter-signup
# ...edit files...
git add .
git commit -m "Add newsletter signup form"
# Push the branch to GitHub
git push -u origin feature/newsletter-signup
# GitHub prints a link to open a pull request
From there you open the PR on github.com, describe what changed and why, and request review. This is the beating heart of professional collaboration β every change gets a second pair of eyes before it touches the shared codebase.
π‘ Forking for open source
To contribute to a project you don't own, you fork it β GitHub makes your own copy. You clone your fork, branch, push, and open a PR from your fork back to the original. Maintainers review and merge. It's the same PR flow, just across two repositories.
The Time Machine: Undoing Things
Here's where Git earns its "time machine" name. There are three undo commands, and the trick is picking the right one for the situation. Choosing wrong is where beginners get burned β so learn the distinction once and you'll never fear an undo again.
| Command | Use when⦠| Safe on shared history? |
|---|---|---|
git restore <file> | You want to discard uncommitted edits to a file | N/A β local only |
git revert <commit> | You want to undo a pushed commit safely | β Yes |
git reset <commit> | You want to rewind local, unpushed commits | β No β rewrites history |
Discard uncommitted changes β git restore
# Throw away edits to one file, reverting it to the last commit
git restore index.html
Undo a commit that's already public β git revert
revert is the polite, team-safe undo. Instead of erasing a commit, it creates a new commit that reverses it β so history stays intact and nobody's clone breaks.
# Undo a specific commit by creating an inverse commit
git revert a1b2c3d
git push # safe to share β it's just a new commit
Rewind local commits β git reset
reset moves your branch pointer backward, as if the later commits never happened. Use it only on commits you have not pushed.
# Undo the last commit but KEEP its changes staged
git reset --soft HEAD~1
# Undo the last commit and unstage the changes (keeps files)
git reset HEAD~1
# Undo the last commit and DISCARD its changes entirely
git reset --hard HEAD~1
β οΈ --hard destroys uncommitted work
git reset --hard throws away changes with no confirmation. It's the one command to run slowly and deliberately. And never reset commits you've already pushed to a shared branch β it rewrites history and forces teammates into a painful cleanup. For anything public, use revert.
Recovering "Lost" Work: reflog
Did a reset --hard or a bad merge seem to vaporize your commits? Take a breath β Git almost never truly deletes anything for weeks. The reflog records every position your branch has ever pointed at, so you can find a "lost" commit and jump back to it.
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: Add feature I thought I lost
# ...
# Recover by pointing back at the commit you want
git reset --hard e4f5g6h
# or create a branch from it without disturbing anything
git switch -c recovered e4f5g6h
β The reflog is your safety net
Because of the reflog, most "I destroyed my repo" panics are recoverable. Before assuming work is gone forever, always run git reflog and look for the commit's ID. It's the single most reassuring command in Git.
Practice & Quiz
ποΈ Exercise 1: Branch, commit, merge
Goal: Build a small feature on its own branch and merge it into main without ever endangering main.
π‘ Hint
Create the branch with git switch -c, make a commit or two, switch back to main, and git merge the branch in. Confirm with git log --oneline --graph that the history shows the branch joining.
β Solution
# 1. Branch off main
git switch -c feature/about-page
# 2. Do the work and commit it
echo "<h1>About</h1>" > about.html
git add about.html
git commit -m "Add about page"
# 3. Merge it back into main
git switch main
git merge feature/about-page
# 4. Visualize and clean up
git log --oneline --graph
git branch -d feature/about-page
ποΈ Exercise 2: Undo the right way
Goal: Make a bad commit, then undo it two different ways β once when it's still local, once as if it were already pushed.
β Solution
# Make a commit you'll pretend is a mistake
echo "oops" > bug.txt
git add bug.txt
git commit -m "Bad commit"
# Case A β still local: rewind it away
git reset --hard HEAD~1 # bug.txt and the commit are gone
# Case B β if it had been pushed: reverse it safely instead
# git revert HEAD # creates a new commit undoing the bad one
If you ever regret the reset --hard, remember: git reflog can bring it back.
π― Quick Quiz
Question 1: Which command creates a new branch and switches to it in one step?
Question 2: You need to undo a commit that's already pushed to a shared branch. What's the safe choice?
Question 3: A reset --hard seems to have deleted a commit. What should you try first?
Best Practices & Pitfalls
β Do
- Do all new work on a branch, keeping
mainalways deployable - Give branches descriptive, prefixed names like
feature/andfix/ - Merge shared work through pull requests so it gets reviewed
- Use
git revertto undo anything already pushed - Reach for
git reflogbefore believing work is truly lost - Delete branches once merged to keep the repo tidy
β Don't
- Commit directly to
mainon a team project β branch and open a PR - Run
git reset --hardon commits you've already pushed - Switch branches with uncommitted work that would be lost β commit or stash first
- Accept "yours" or "theirs" in a conflict without reading both sides
- Panic and re-clone; you'll lose local commits that a
reflogcould have saved
β οΈ "Detached HEAD" is not an error
If you check out a specific commit (rather than a branch) you'll see You are in 'detached HEAD' state. It just means you're viewing history, not on a branch. Get back with git switch main. If you made commits you want to keep there, create a branch first: git switch -c my-branch.
Summary
π Key Takeaways
- A branch is a cheap parallel line of work; keep
mainstable and experiment on branches - Create and move with
git switch -candgit switch; merge finished work withgit merge - Teams collaborate through the branch β push β pull request β review β merge flow
- Pick the right undo:
restorefor uncommitted edits,revertfor pushed commits,resetfor local ones git reflogis your safety net β most "lost" work is recoverable
π Additional Resources
- Pro Git β Branches in a Nutshell
- git-scm.com β git switch documentation
- GitHub Docs β About pull requests
- GitHub Docs β Contributing to a project (fork & PR)
π What's Next?
You can now branch, merge, collaborate, and undo with confidence β the complete Git toolkit for real teamwork. Next up, put an AI pair programmer to work inside your editor with Mastering GitHub Copilot: Advanced AI Pair Programming.
π You control the timeline now
Branch fearlessly, merge deliberately, and remember: with Git, almost nothing is ever truly lost.