## Git’s Three Areas Git separates your project into three useful **areas**: the **Working Directory** (the files you are editing), the **Staging Area**, also called the **index** (your deliberate draft of the next snapshot), and the **Local Repository** (the saved commit history). Think of staging as choosing items for a shipment: changing a file does not put it in the box; `git add` does. Git stores the history and metadata in a hidden **`.git` directory**, separate from the ordinary project files you see and edit. That separation is why Git can compare what you have now with what you staged and what you last committed. [A Grip On Git](https://agripongit.vincenttunru.com/) describes this model and the hidden repository directory. <viz id="0"></viz> **Edit the file**, then **run `git status`**. **Click `git add`** and inspect status again; then **use `git restore --staged`** to see that unstaging keeps your working edit. Finally, **stage and commit** the change. The key distinction is scope: `git add` changes what the *next commit* will contain, while `git commit` writes that staged selection into history. An edit can therefore be real in your files yet absent from both the staging area and repository. That extra step may seem fussy, but it lets you group related changes into one explainable unit. Next, you will use that control to create snapshots that are genuinely useful to future you and your teammates: <ref slide="2">Save a Meaningful Snapshot</ref>. ## Save a Meaningful Snapshot A **commit** is a named snapshot of exactly what is staged, plus a link to its **parent** commit. It is not simply “save everything I touched today.” By staging only related file versions, you make one small, focused claim about how the project changed—for example, “validate email input,” rather than “miscellaneous work.” A concise **commit message** records the purpose of that change. Focused commits are practical: `git diff` can expose the work not yet committed, `git log` can explain the sequence of saved decisions, and a problematic change can be isolated later without disturbing unrelated work. <viz id="1"></viz> **Stage only one file**, write a purpose-focused message, and **commit it**. Then **press `git diff`** to find the remaining edit, and **press `git log`** to inspect the new snapshot in sequence. **Try resetting the scenario** and commit both files to compare the two histories. A commit captures versions, not merely filenames: if only one edited file is staged, only that file’s staged version enters the new node. The other edit remains available for a later, separate decision. This is the payoff of the three-area model in <ref slide="1">Git’s Three Areas</ref>. Each new commit extends a graph rather than a flat list. That structure becomes especially powerful when different lines of work need to coexist. ## Branches Are Movable Labels A **branch** is not a separate copy of your whole project. It is a movable label pointing to one commit in the graph. **`HEAD`** tells Git what you currently have checked out—the commit and branch context on which your next commit will be based. Creating a branch with `git branch feature` places a new label at the current commit; it does not switch you there. `git switch feature` moves `HEAD` to that branch and updates the working files to match its commit. When you commit, the checked-out branch label advances, while `main` stays where it was. This pointer model is the core idea behind the branch workflow described in [A Grip On Git](https://agripongit.vincenttunru.com/). <viz id="2"></viz> **Create `feature`**, and notice that `HEAD` still belongs with `main`. **Switch to `feature`**, make a commit, then **switch back to `main`**. Watch both the labels and the displayed project files. The two branches share every commit before they diverge. Only after a new commit on `feature` do the labels point to different project states. Switching is therefore not a merge or a copy operation: it selects a different saved snapshot for your working directory. Once there are multiple pointers, reading the graph is a skill in its own right. You will next inspect a commit, compare two snapshots, and move through history without changing it. ## Explore and Compare History Git history is a **commit graph**: each commit carries a message and records a project state relative to its parent. Exploration commands answer different questions. **`git log`** asks, “What sequence of commits led here?” **`git show`** asks, “What did this particular commit introduce?” **`git diff`** asks, “How do these two versions differ?” A **diff** is a line-level comparison, commonly showing removed and added text. It is a decision-making tool: before integrating a branch or undoing a change, inspect the actual patch rather than relying only on a commit message. <viz id="3"></viz> **Click a commit** and **run `git show`** to inspect its patch. **Select two commits** and **run `git diff`** to compare their file versions. Then **scrub through the graph** and observe how the project files change without altering any branch. The same graph can support several views because commits are stable reference points. `git log` gives the story, `git show` gives one scene, and `git diff` gives the precise before-and-after evidence. A focused commit from <ref slide="2">Save a Meaningful Snapshot</ref> makes all three views easier to understand. History inspection also lets you verify whether two lines of work can be combined cleanly. That is the question a merge answers. ## Merge Branches Together A **merge** integrates the history reachable from another branch into the branch you currently have checked out. The current branch receives the result. So if you switch to `main` and run `git merge feature`, you are bringing `feature` into `main`—not the reverse. When `main` has no commits that `feature` lacks, Git can perform a **fast-forward**: it simply moves the `main` label forward, creating no new commit. When both branches have unique commits, Git creates a **merge commit** with two parents, preserving the fact that two development paths came together. [This visual Git guide](https://dev.to/lydiahallie/cs-visualized-useful-git-commands-37p1) illustrates both cases. <viz id="4"></viz> **Choose the straight-ahead scenario**, check out `main`, and **merge `feature`**. Then **choose the diverged scenario** and repeat. **Compare the graph**: look for label movement in one case and a new two-parent node in the other. A merge commit is historical information, not duplication: it says these two lines of development were reconciled at this point. A fast-forward says there was nothing on the receiving branch that needed reconciling. Git can often combine independent edits automatically. But when both branches prescribe incompatible text for the same place, the correct result is not a graph problem—it is a human judgment. ## Resolve Conflicts Deliberately A **merge conflict** occurs when Git cannot safely choose between incompatible changes—for example, when `main` and `feature` both modify the same line differently. Git stops rather than guessing. This is a safeguard: the conflict means the repository needs your intended final version. Conflict markers separate the competing sections. Your job is to edit them into one valid **resolution**: keep `main`’s version, keep `feature`’s version, or write a combined version that satisfies the actual requirement. Then stage that resolved file and commit to finish the merge. The edit–add–commit completion flow is also described in [CS Visualized](https://dev.to/lydiahallie/cs-visualized-useful-git-commands-37p1). <viz id="5"></viz> **Start the merge** and read both conflicting versions. **Choose or write a resolution** that makes the configuration valid. Then **run `git add`** and **commit the merge**. Notice that editing alone does not complete the merge. Treat a conflict as a review moment. Check what each branch was trying to accomplish, test the resolved file if possible, and ensure no marker text remains. The merge only becomes a completed shared snapshot after you explicitly stage and commit it. That deliberate control is the same principle behind safe recovery: before changing history or files, identify exactly which state you want to affect. ## Undo Without Losing Work “Undo” in Git is several different operations. Choose by asking two questions: *Where is the change now?* and *Do other people rely on this history?* **`git restore`** discards an unstaged working-file edit. **`git restore --staged`** removes a change from the staging area but keeps the working-file edit. Neither creates a commit. **`git revert`** makes a new commit whose change is the inverse of an earlier commit. Because it preserves the existing graph and adds an explicit correction, it is the safer default for shared history. **`git reset --soft`** instead moves the current branch label backward while retaining the undone commit’s changes in the staging area—useful for reshaping local work, but a history rewrite if the old tip was already shared. <viz id="6"></viz> For each command, **make a prediction first** about the working area, staging area, and graph. Then **run `git restore`**, **`git restore --staged`**, **`git revert`**, and **`git reset --soft`** on the matching scenario. **Compare the final graph states**. The crucial contrast is between changing files, changing the staged proposal, and changing the branch’s visible history. `git revert` does not erase the original commit; it records a later decision that cancels its effect. `git reset --soft` does not throw away the content either, but it repositions the branch pointer and leaves the content ready to recommit. Once you work with others, this distinction matters even more: your local graph is connected to a remote shared graph. ## Sync With GitHub A **remote repository** is another Git repository, often hosted on GitHub. By convention, the remote named **`origin`** is the one you cloned from. Your local `main` and the remote-tracking reference `origin/main` are separate pointers: the latter is your local record of where Git last learned the remote branch was. `git clone` creates a local repository from a remote. `git fetch` downloads remote updates and advances remote-tracking references, without changing your checked-out `main`. `git pull` obtains updates and integrates them into your current branch. `git push` asks the remote to advance its branch with your local commits. A rejected push usually means the remote contains commits you have not integrated; fetch or pull, inspect and resolve any integration work, then push again. <viz id="7"></viz> **Clone the repository** and inspect the two sets of pointers. After a teammate adds a remote commit, **run `git fetch`** and note what stays unchanged locally. **Try pushing** to trigger rejection, then **pull and integrate** the update before **pushing again**. Syncing is pointer coordination between repositories, not a magical replacement of one project with another. Fetch is low-risk information gathering; pull is the moment your local branch actually integrates that information; push proposes your local commits to the shared remote. Use the graph-reading skills from <ref slide="4">Explore and Compare History</ref> before integration, and the merge and conflict workflow from <ref slide="5">Merge Branches Together</ref> and <ref slide="6">Resolve Conflicts Deliberately</ref> when collaboration produces diverging changes. With that model, Git commands become predictable movements of files, snapshots, and labels.