Skip to main content

Git Fundamentals

Master the core concepts that make Git powerful: repositories, commits, and the staging area.

Installing and Configuring Git

First-Time Configuration


Creating Your First Repository

Method 1: Initialize a New Repository

The .git directory contains all Git metadata: commits, branches, configuration, and the entire history.

Method 2: Clone an Existing Repository


The Git Workflow

Git Workflow

1. Working Directory

Your actual files where you make changes.

2. Staging Area (Index)

Prepare changes for commit. The staging area is Git’s “shopping cart” — you browse the store (make changes in your working directory), add items to the cart (stage them), and checkout (commit) only when you are ready. You can add and remove items from the cart before committing, which means you control exactly what goes into each commit.
Why a staging area? It lets you craft precise commits. You can modify 10 files but commit only 3 related changes.

3. Repository

Committed snapshots stored permanently.

Understanding Commits

A commit is a snapshot of your entire project at a point in time. Not a diff, not a delta — a full snapshot. Git is smart enough to deduplicate identical files across snapshots (using content-addressable hashing), so this is efficient despite sounding wasteful. Every commit also records who made it, when, and a message explaining why.

Anatomy of a Commit

Writing Good Commit Messages

Commit Message Best Practices:
  • Use present tense: “Add feature” not “Added feature”
  • Be specific: “Fix login timeout” not “Fix bug”
  • Keep first line under 50 characters
  • Add details in the body if needed
  • Reference issue numbers: “Fix #123: resolve login issue”

Checking Status and History

Git Status

Output explained:

Git Log


Viewing Changes

Git Diff


Ignoring Files

Create a .gitignore file to exclude files from version control. This is not optional — without it, you will inevitably commit node_modules (300MB of dependencies), .env files (database passwords), or OS junk files (.DS_Store) that have no business in a repository.
Critical gotcha: .gitignore only prevents untracked files from being added. If you already committed a file and then add it to .gitignore, it is NOT removed from tracking. You must explicitly untrack it: git rm --cached .env (removes from tracking but keeps the file on disk). This catches many beginners who accidentally commit secrets, add .env to .gitignore, and assume the secret is gone. It is still in the Git history.
Use gitignore.io to generate .gitignore templates for your tech stack.

Undoing Changes

Unstage Files

Discard Changes

git restore without --staged permanently deletes uncommitted changes!

Amend Last Commit


Practical Example: First Project

Let’s create a complete example:

Common Workflows

Daily Development

Quick Fixes


Key Takeaways

Three States

Modified → Staged → Committed

Commit Often

Small, logical commits are better than large ones

Good Messages

Future you will thank present you

Check Status

Run git status frequently

Practice Exercises

1

Exercise 1: Basic Workflow

  1. Create a new repository
  2. Add 3 files
  3. Make 5 commits
  4. View the log with --oneline --graph
2

Exercise 2: Staging

  1. Modify 3 files
  2. Stage only 2 of them
  3. Commit the staged files
  4. Commit the remaining file separately
3

Exercise 3: Undoing

  1. Make a commit with a typo in the message
  2. Amend the commit
  3. Make changes to a file
  4. Discard those changes

Interview Deep-Dive

Strong Answer:
  • In Git, a file passes through three states: modified (changed in your working directory but not yet staged), staged (marked for inclusion in the next commit via git add), and committed (safely stored in the repository’s history as an immutable snapshot).
  • The staging area (also called the index) exists because it gives you editorial control over your commits. In SVN or Mercurial, commit grabs everything that changed. In Git, you decide exactly what goes in each commit. If you changed 10 files but only 3 are related to the bug fix you are committing, you stage those 3, commit with a clear message, then handle the remaining 7 in separate commits.
  • This enables atomic, logical commits. A single coding session might touch error handling, add a feature, and fix a typo. Without a staging area, all three go into one messy commit. With staging, you create three clean commits that can be reviewed, reverted, and bisected independently.
  • git add -p takes this further by letting you stage individual hunks within a file. You can commit the bug fix on line 42 but leave the debug logging on line 100 unstaged. This level of granularity is impossible without a staging area.
  • Under the hood, the staging area is a binary file (.git/index) that records which file versions (blob hashes) are ready for the next commit. git status compares three things: working directory vs index (unstaged changes), index vs HEAD commit (staged changes). This two-comparison model is why git diff and git diff --staged show different things.
Follow-up: A developer on your team always uses ‘git add .’ and commits everything at once. How would you coach them toward better practices?I would demonstrate the value through a code review. I would show them a PR with 5 unrelated changes in one commit versus the same changes split into 5 logical commits. The second version is reviewable, revertable, and bisectable. I would also show them git add -p in action — most developers are amazed the first time they see partial staging. The concrete habit I recommend: before committing, always run git diff --staged to review exactly what is going in. If the diff contains unrelated changes, unstage some with git restore --staged <file> and commit in batches.
Strong Answer:
  • The secret is not safe. Adding a file to .gitignore only prevents future untracked files from being staged. It does not remove files that are already tracked. The .env file is still in the Git history — anyone who clones the repository or browses the commit history can see it. git log -p -- .env will show the full contents.
  • Immediate actions: First, rotate the database credentials right now. Assume they are compromised. Second, remove the file from tracking without deleting it from disk: git rm --cached .env followed by a commit. This stops Git from tracking changes to .env going forward, but the historical commit still contains the file.
  • To remove the file from all history, use git filter-repo --path .env --invert-paths. This rewrites every commit that touched .env, removing it entirely. This changes commit hashes, so all team members must re-clone or reset to the rewritten history. For public repositories on GitHub, you also need to contact GitHub support to purge cached data, because force-pushing does not immediately clear their CDN caches.
  • Prevention: set up a pre-commit hook (using Husky or a custom hook) that scans staged files for patterns matching secrets (API keys, passwords, connection strings). Tools like git-secrets (AWS), gitleaks, or ggshield automate this. Also, use .env.example with placeholder values committed to the repo, and .env in .gitignore from the very first commit.
Follow-up: The repository is public on GitHub. How long do you have before the credential is likely compromised?Minutes, not hours. Automated bots continuously scan public GitHub commits for secrets. Researchers have demonstrated that AWS keys committed to public repos are exploited within 5 minutes of the push. This is why credential rotation is the first step, not the last. History rewriting is important for long-term hygiene, but it does not undo exposure that has already happened.
Strong Answer:
  • All three move the branch pointer (HEAD) to a different commit. The difference is what happens to the staging area (index) and the working directory.
  • --soft moves HEAD to the target commit but leaves the index and working directory unchanged. Everything that was in the “undone” commits becomes staged. Scenario: you made 5 small commits that should have been one. git reset --soft HEAD~5 collapses them all into staged changes, and you re-commit with a single clean message. No work is lost.
  • --mixed (the default) moves HEAD and resets the index to match the target commit, but leaves the working directory unchanged. Previously committed and staged changes become unstaged modifications. Scenario: you staged a file by mistake with git add . and want to unstage everything. git reset HEAD (which is --mixed by default) unstages all files but keeps your edits.
  • --hard moves HEAD, resets the index, and overwrites the working directory to match the target commit. Uncommitted changes are permanently deleted. Scenario: your local branch is hopelessly mangled after a bad rebase, and you want to start fresh from the remote. git reset --hard origin/main discards everything local and syncs to the remote. Use with extreme caution — there is no undo for uncommitted work destroyed by --hard.
  • The mental model: --soft touches only the commit history, --mixed touches history and staging, --hard touches everything. Each level is more destructive than the previous.
Follow-up: You accidentally ran ‘git reset —hard HEAD~3’ and lost uncommitted changes that were in your working directory. Can you recover them?The committed changes (the 3 commits you reset past) are recoverable via git reflog — find the SHA and create a branch. But uncommitted changes that were in the working directory are gone forever. Git never tracked them, so there is no reflog entry. The only hope is IDE local history (VS Code, IntelliJ, and others maintain their own file history) or filesystem-level recovery (Time Machine, file system snapshots). This is why I always tell developers: commit early and often, even WIP commits. A git reset --soft later can clean up the history, but uncommitted work is the one thing Git cannot protect.

Next: Git Branching & Merging →