> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Git Fundamentals

> Core Git concepts and basic commands

# Git Fundamentals

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

***

## Installing and Configuring Git

<Tabs>
  <Tab title="Windows">
    ```bash theme={null}
    # Download from git-scm.com or use winget
    winget install Git.Git

    # Verify installation
    git --version
    ```
  </Tab>

  <Tab title="macOS">
    ```bash theme={null}
    # Using Homebrew
    brew install git

    # Or install Xcode Command Line Tools
    xcode-select --install

    # Verify
    git --version
    ```
  </Tab>

  <Tab title="Linux">
    ```bash theme={null}
    # Ubuntu/Debian
    sudo apt update
    sudo apt install git

    # RHEL/CentOS
    sudo yum install git

    # Verify
    git --version
    ```
  </Tab>
</Tabs>

### First-Time Configuration

```bash theme={null}
# Set your identity (required)
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

# Set default branch name
git config --global init.defaultBranch main

# Set default editor
git config --global core.editor "code --wait"  # VS Code
# or
git config --global core.editor "vim"

# View all settings
git config --list
```

***

## Creating Your First Repository

### Method 1: Initialize a New Repository

```bash theme={null}
# Create a new directory
mkdir my-project
cd my-project

# Initialize Git repository
git init

# This creates a hidden .git directory
ls -la
```

<Info>
  The `.git` directory contains all Git metadata: commits, branches, configuration, and the entire history.
</Info>

### Method 2: Clone an Existing Repository

```bash theme={null}
# Clone from GitHub
git clone https://github.com/username/repo.git

# Clone with a different name
git clone https://github.com/username/repo.git my-folder

# Clone a specific branch
git clone -b develop https://github.com/username/repo.git
```

***

## The Git Workflow

<img src="https://mintcdn.com/devweeekends/emzPt-9B_R8UKdqm/images/courses/devops-tools/git-workflow.svg?fit=max&auto=format&n=emzPt-9B_R8UKdqm&q=85&s=73bbc32719156c1b5c2700cd3c37d0c0" alt="Git Workflow" width="1080" height="1080" data-path="images/courses/devops-tools/git-workflow.svg" />

### 1. Working Directory

Your actual files where you make changes.

```bash theme={null}
# Create a new file
echo "# My Project" > README.md

# Edit files with your editor
code index.html
```

### 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.

```bash theme={null}
# Stage a single file
git add README.md

# Stage all changes
git add .

# Stage specific files
git add file1.txt file2.txt

# Stage all .js files
git add *.js

# Interactive staging
git add -p  # Choose which changes to stage
```

<Tip>
  **Why a staging area?** It lets you craft precise commits. You can modify 10 files but commit only 3 related changes.
</Tip>

### 3. Repository

Committed snapshots stored permanently.

```bash theme={null}
# Commit staged changes
git commit -m "Add README and initial HTML"

# Commit with detailed message
git commit  # Opens editor for multi-line message

# Stage and commit in one step (tracked files only)
git commit -am "Update homepage content"
```

***

## 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

```bash theme={null}
commit abc123def456  # SHA-1 hash (unique identifier)
Author: John Doe <john@example.com>
Date:   Mon Dec 2 10:30:00 2024 +0500

    Add user authentication feature
    
    - Implement login/logout
    - Add password hashing
    - Create user session management
```

### Writing Good Commit Messages

<CodeGroup>
  ```bash Good Examples theme={null}
  git commit -m "feat: add user authentication"
  git commit -m "fix: resolve memory leak in image processor"
  git commit -m "docs: update API documentation"
  git commit -m "refactor: simplify database query logic"
  ```

  ```bash Bad Examples theme={null}
  git commit -m "update"
  git commit -m "fix stuff"
  git commit -m "asdfasdf"
  git commit -m "final version"  # There's always another "final"
  ```
</CodeGroup>

<Warning>
  **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"
</Warning>

***

## Checking Status and History

### Git Status

```bash theme={null}
# See what's changed
git status

# Short format
git status -s
```

**Output explained**:

```
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
        modified:   style.css

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        script.js
```

### Git Log

```bash theme={null}
# View commit history
git log

# One line per commit
git log --oneline

# With graph
git log --oneline --graph --all

# Last 5 commits
git log -5

# Commits by author
git log --author="John"

# Commits in date range
git log --since="2 weeks ago"

# Show changes in each commit
git log -p
```

***

## Viewing Changes

### Git Diff

```bash theme={null}
# Changes in working directory (not staged)
git diff

# Changes in staging area
git diff --staged

# Changes between commits
git diff abc123 def456

# Changes in a specific file
git diff README.md

# Word-level diff
git diff --word-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.

```gitignore theme={null}
# .gitignore

# Dependencies -- always reinstall from lockfile; host binaries may not match container/server
node_modules/
vendor/

# Build outputs -- generated from source; committing them causes constant merge conflicts
dist/
build/
*.exe
*.dll

# Environment files -- contain secrets (API keys, DB passwords). NEVER commit these.
.env
.env.local

# IDE files -- personal preferences that differ per developer
.vscode/
.idea/
*.swp

# OS files -- macOS and Windows generate these automatically
.DS_Store
Thumbs.db

# Logs -- ephemeral runtime output, not source code
*.log
logs/

# Temporary files
*.tmp
temp/
```

<Warning>
  **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.
</Warning>

<Tip>
  Use [gitignore.io](https://www.toptal.com/developers/gitignore) to generate `.gitignore` templates for your tech stack.
</Tip>

***

## Undoing Changes

### Unstage Files

```bash theme={null}
# Unstage a file (keep changes in working directory)
git restore --staged file.txt

# Unstage all files
git restore --staged .
```

### Discard Changes

```bash theme={null}
# Discard changes in working directory (DANGEROUS!)
git restore file.txt

# Discard all changes
git restore .
```

<Warning>
  `git restore` without `--staged` **permanently deletes** uncommitted changes!
</Warning>

### Amend Last Commit

```bash theme={null}
# Fix the last commit message
git commit --amend -m "New message"

# Add forgotten files to last commit
git add forgotten-file.txt
git commit --amend --no-edit
```

***

## Practical Example: First Project

Let's create a complete example:

```bash theme={null}
# 1. Create project
mkdir my-website
cd my-website
git init

# 2. Create files
echo "# My Website" > README.md
echo "<!DOCTYPE html><html><body>Hello</body></html>" > index.html

# 3. Check status
git status

# 4. Stage files
git add README.md index.html

# 5. Commit
git commit -m "Initial commit: add README and index page"

# 6. Make changes
echo "<p>Welcome!</p>" >> index.html

# 7. See what changed
git diff

# 8. Stage and commit
git add index.html
git commit -m "Add welcome message to homepage"

# 9. View history
git log --oneline
```

***

## Common Workflows

### Daily Development

```bash theme={null}
# Start your day
git status                    # See where you left off
git pull origin main          # Get latest changes

# Work on feature
git checkout -b feature/new-ui
# ... make changes ...
git add .
git commit -m "feat: redesign homepage UI"

# Push your work
git push origin feature/new-ui
```

### Quick Fixes

```bash theme={null}
# Oops, typo in last commit
git add typo-file.txt
git commit --amend --no-edit

# Discard all uncommitted changes
git restore .
git clean -fd  # Remove untracked files
```

***

## Key Takeaways

<CardGroup cols={2}>
  <Card title="Three States" icon="layer-group">
    Modified → Staged → Committed
  </Card>

  <Card title="Commit Often" icon="clock">
    Small, logical commits are better than large ones
  </Card>

  <Card title="Good Messages" icon="message">
    Future you will thank present you
  </Card>

  <Card title="Check Status" icon="list-check">
    Run `git status` frequently
  </Card>
</CardGroup>

***

## Practice Exercises

<Steps>
  <Step title="Exercise 1: Basic Workflow">
    1. Create a new repository
    2. Add 3 files
    3. Make 5 commits
    4. View the log with `--oneline --graph`
  </Step>

  <Step title="Exercise 2: Staging">
    1. Modify 3 files
    2. Stage only 2 of them
    3. Commit the staged files
    4. Commit the remaining file separately
  </Step>

  <Step title="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
  </Step>
</Steps>

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="Explain the three states of a file in Git (modified, staged, committed) and why the staging area exists. Most version control systems go straight from 'changed' to 'committed' -- what does Git gain from the extra step?" icon="circle-question">
    **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.
  </Accordion>

  <Accordion title="A new developer on your team accidentally committed a .env file containing database credentials to the repository. They then added .env to .gitignore and made another commit. Is the secret safe now? What do you do?" icon="circle-question">
    **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.
  </Accordion>

  <Accordion title="What is the difference between 'git reset --soft', '--mixed', and '--hard'? Give me a scenario where you would use each." icon="circle-question">
    **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.
  </Accordion>
</AccordionGroup>

***

Next: [Git Branching & Merging →](/courses/devops-tools/git-branching)
