Tools & Workflow

Git for beginners: the mental model, the ten commands, and how to undo things

What Git actually stores, the working tree / staging area / repository model that explains every command, the daily workflow with branches and pull requests, and a table of safe ways to undo mistakes.

By the TechTutorZone editorial team · Published · 6 min read (1,234 words)

Git is easier to learn if you understand what it is doing than if you memorise commands. It stores snapshots of your whole project, each one pointing at the one before, and a branch is just a name for the latest snapshot in a chain. Everything else, staging, merging, rebasing, pushing, is a way of creating or moving those snapshots and names. This tutorial gives you that model first, then the handful of commands you will use every day, then the thing beginners fear most: undoing.

Three places your files live

  1. The working tree: the actual files in your folder, the ones your editor sees.
  2. The staging area (also called the index): a draft of the next snapshot. You put changes here with git add.
  3. The repository: the .git folder holding every committed snapshot. git commit turns the staging area into a new snapshot.

Nearly every command moves changes between these three. git status tells you what is in each; run it constantly.

Setup, once

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

The name and email go into every commit you make and are visible to anyone with the repository. GitHub offers a private no-reply address under Settings, Emails, if you would rather not publish yours.

The daily loop

$ git init                  # once per project: creates .git
$ git status                # what changed?
$ git add src/app.js        # stage one file (git add . stages everything)
$ git diff --staged         # review exactly what will be committed
$ git commit -m "Add task filtering"
$ git log --oneline         # history, newest first

Commit small and often: one logical change per commit, with a message that says what it does in the imperative ("Add", "Fix", "Remove"), because the history is read as a list of changes. git add . is convenient but stages everything, including files you did not mean to commit; glance at git status first, and add a .gitignore file listing node_modules/, .env and build output before your first commit.

Branches

A branch is a movable label on a commit. Creating one costs nothing, and working on a branch means the main line of history stays clean until the change is ready.

$ git switch -c fix-login    # create and move to a branch
# ...edit, add, commit as usual...
$ git switch main            # back to main
$ git merge fix-login        # bring the branch's commits into main
$ git branch -d fix-login    # delete the label (the commits stay)

If main has not changed since you branched, the merge is a "fast-forward": the main label simply moves to your latest commit. If both have changed, Git creates a merge commit with two parents. If both changed the same lines, it stops and asks you to resolve the conflict: open the file, find the <<<<<<< / ======= / >>>>>>> markers, keep what should be kept, delete the markers, git add the file and git commit. A conflict is not an error; it is Git refusing to guess.

Working with GitHub

A remote is a copy of the repository somewhere else, usually GitHub, GitLab or a private server. origin is the conventional name for the main one.

$ git clone https://github.com/someone/project.git   # copy an existing repo
$ git remote add origin git@github.com:you/project.git  # or link a new local repo to one you created on GitHub
$ git push -u origin main    # first push sets the upstream; afterwards just: git push
$ git pull                   # fetch others' commits and merge them into your branch

The pull request workflow, which almost every team uses: branch, commit, git push -u origin your-branch, open a pull request on the website, get a review, merge there, then git switch main && git pull locally. Pull requests are a GitHub feature, not a Git one; Git only knows about branches.

Authentication: GitHub no longer accepts account passwords over HTTPS. Use an SSH key (ssh-keygen -t ed25519, then add the public key under Settings, SSH keys) or the GitHub CLI's gh auth login, which sets up a token for you.

Undoing things safely

The rule: anything committed is recoverable for weeks (see git reflog); uncommitted changes that you discard are gone. So commit before any risky operation, and prefer the commands that create new history over the ones that rewrite it.

SituationCommandRewrites history?
Discard uncommitted edits to one filegit restore fileNo, but the edits are lost
Unstage a file (keep the edits)git restore --staged fileNo
Fix the message or add a file to the last commit (not yet pushed)git commit --amendYes, last commit only
Undo a commit that was already pushedgit revert <hash>No: adds a new commit that reverses it
Throw away the last commit entirely (not pushed)git reset --hard HEAD~1Yes; working tree changes are lost too
Keep the last commit's changes but uncommit themgit reset --soft HEAD~1Yes, but nothing is lost
Find a commit you "lost" with resetgit reflog then git switch -c rescue <hash>No
Park half-done work to switch branchesgit stash / git stash popNo

Never rewrite history that others have pulled: amending or resetting a pushed commit and then git push --force deletes it from their point of view and causes exactly the conflicts you were trying to avoid. On your own unpushed branch, rewrite freely.

Reading the history

$ git log --oneline --graph --all     # the branch structure as ASCII art
$ git show HEAD~2                     # one commit's message and diff
$ git blame src/app.js                # who last changed each line, and in which commit
$ git log -S "functionName"           # commits that added or removed that string

HEAD is the commit you are on; HEAD~2 is two commits back. Commit hashes can be abbreviated to the first seven or so characters.

Pitfalls

  • Committing secrets. A pushed .env with an API key is compromised even after you delete it, because the history keeps it. Rotate the key; add the file to .gitignore.
  • Huge commits. "Changes" with 40 files touched cannot be reviewed or reverted usefully.
  • Pulling with a dirty working tree. Commit or stash first; otherwise a merge can tangle with your uncommitted edits.
  • Committing node_modules. Thousands of files, hundreds of megabytes, all regenerable with npm install.
  • Line endings. Windows and Unix differ; set git config --global core.autocrlf (true on Windows, input elsewhere) or add a .gitattributes with * text=auto, before whole files show as changed.

Exercise

Make a new folder with git init, commit a text file, then make three commits on a branch called experiment. Merge it into main. Now create a conflict on purpose: change the same line on main and on a new branch, merge, and resolve it. Finally, run git reset --hard HEAD~1 to "lose" the merge, then use git reflog to get it back. You will never fear reset again.

Summary

  • Git stores snapshots; branches are labels on them; the staging area drafts the next one.
  • Daily: status, add, diff --staged, commit, push, pull.
  • Branch for every change; merge or open a pull request; delete the branch after.
  • revert for pushed mistakes, reset and amend only for unpushed ones, reflog when all seems lost.