Skip to main content

⏳ 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 switch and git switch -c
  • Merge a finished branch back into main and understand fast-forward vs merge commits
  • Collaborate through the GitHub fork β†’ branch β†’ pull request workflow
  • Undo mistakes with the right tool: restore, revert, or reset
  • Recover "lost" work using git reflog when 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.

A feature branch splitting off main and merging back after two commits main merge feature/search
A feature branch splits off 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:

PatternExampleMeaning
feature/feature/user-loginA new capability
fix/fix/cart-total-roundingA bug fix
docs/docs/api-readmeDocumentation 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:

sequenceDiagram participant Dev as Developer participant Main as main branch participant Feat as feature branch Dev->>Feat: Create branch and switch to it Dev->>Feat: Commit the new feature work Dev->>Main: Switch back to main Dev->>Main: Merge the feature branch in Main-->>Dev: Confirm main now includes the feature Dev->>Feat: Delete the finished branch

⚠️ 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.

graph TD A["Create a feature branch"] --> B["Commit your work"] B --> C["Push the branch to GitHub"] C --> D["Open a Pull Request"] D --> E["Teammates review and comment"] E --> F{"Approved?"} F -->|Changes requested| B F -->|Yes| G["Merge the PR into main"] G --> H["Delete the branch"]

πŸ’‘ 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.

CommandUse when…Safe on shared history?
git restore <file>You want to discard uncommitted edits to a fileN/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.

graph TD A["I need to undo something"] --> B{"Was it committed?"} B -->|Not yet, just file edits| C["git restore the file"] B -->|Committed but not pushed| D["git reset to rewind"] B -->|Already pushed and shared| E["git revert to reverse safely"]

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 main always deployable
  • Give branches descriptive, prefixed names like feature/ and fix/
  • Merge shared work through pull requests so it gets reviewed
  • Use git revert to undo anything already pushed
  • Reach for git reflog before believing work is truly lost
  • Delete branches once merged to keep the repo tidy

❌ Don't

  • Commit directly to main on a team project β€” branch and open a PR
  • Run git reset --hard on 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 reflog could 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 main stable and experiment on branches
  • Create and move with git switch -c and git switch; merge finished work with git merge
  • Teams collaborate through the branch β†’ push β†’ pull request β†’ review β†’ merge flow
  • Pick the right undo: restore for uncommitted edits, revert for pushed commits, reset for local ones
  • git reflog is your safety net β€” most "lost" work is recoverable

πŸ“š Additional Resources

πŸš€ 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.