diff --git a/CLAUDE.md b/CLAUDE.md index 6c6eac5..7974b4b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,7 +46,7 @@ assets/ banners/ doc/ uml/ -astro-site/ # Astro build (imports from chapters/ and assets/) +astro-site/ # Astro build (reads directly from chapters/) docs/ # Legacy MkDocs content (reference, not active) solid-ai-templates/ # Submodule — Imbra-Ltd/solid-ai-templates ``` @@ -109,6 +109,9 @@ Every chapter follows this structure: - Reference other chapters by file: `[Building Blocks](02-building-blocks.md)` - Reference sections within a chapter by heading anchor: `[Tag Object](#tag-object-labels)` +- The remark plugin (`astro-site/src/plugins/remark-rewrite-links.ts`) + rewrites `NN-slug.md` links to `../slug/` at build time — no manual + sync needed ### Images diff --git a/astro-site/astro.config.mjs b/astro-site/astro.config.mjs index 820c50b..4dac08b 100644 --- a/astro-site/astro.config.mjs +++ b/astro-site/astro.config.mjs @@ -1,5 +1,6 @@ // @ts-check import { defineConfig } from 'astro/config'; +import { remarkRewriteLinks } from './src/plugins/remark-rewrite-links.ts'; // https://astro.build/config export default defineConfig({ @@ -8,5 +9,6 @@ export default defineConfig({ trailingSlash: 'always', markdown: { syntaxHighlight: false, + remarkPlugins: [remarkRewriteLinks], }, }); diff --git a/astro-site/src/content.config.ts b/astro-site/src/content.config.ts index b5008b4..e33a8e2 100644 --- a/astro-site/src/content.config.ts +++ b/astro-site/src/content.config.ts @@ -4,7 +4,7 @@ import { glob } from "astro/loaders"; const docs = defineCollection({ loader: glob({ pattern: "**/*.md", - base: "./src/content/docs", + base: "../chapters", generateId: ({ entry }) => entry.replace(/\.md$/, ""), }), schema: z.object({ diff --git a/astro-site/src/content/docs/appendix.md b/astro-site/src/content/docs/appendix.md deleted file mode 100644 index f2cc52b..0000000 --- a/astro-site/src/content/docs/appendix.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -title: "Appendix" -section: "appendix" -order: 8 ---- - -## 1. Overview - -This appendix contains reference material that supports the tutorial -chapters — merge strategies, Git clients, external resources, and -notes. For step-by-step recipes, see the [Playbook](../playbook/). -For command help, run `git help `. - -In this chapter you will learn: - -- Merge strategies — when Git uses each strategy and how to force one -- SSH key setup — generate keys, configure the agent, and connect to GitHub -- Git clients — popular graphical tools for each platform -- References — books, troubleshooting guides, visualizations, and workflow models - -## 2. Merge Strategies - -Git selects a merge strategy automatically. You can force one with -`git merge -s `. - -| Strategy | When Git uses it | What it does | -|----------|-----------------|--------------| -| `recursive` | Default for two branches | 3-way merge; handles multiple common ancestors (criss-cross) | -| `ort` | Default in Git 2.34+ | Faster replacement for recursive; same behavior | -| `octopus` | Default when merging 3+ branches | Merges all at once; fails if any conflicts | -| `ours` | Manual only (`-s ours`) | Records merge but ignores incoming changes entirely | -| `subtree` | Manual only (`-s subtree`) | Adjusts paths when one branch is a subdirectory of another | - -> **Note:** Do not confuse the **ours strategy** (`-s ours`) with the -> **ours option** (`-X ours`). The strategy discards the entire branch. -> The option resolves individual conflicts by preferring the current -> branch but still includes non-conflicting changes. - -## 3. SSH Key Setup - -SSH lets you authenticate with remotes without entering a password -each time. This is the recommended method for frequent use. - -### Generate a key - -```text -$ ssh-keygen -t ed25519 -C "you@example.com" -``` - -Accept the default file location (`~/.ssh/id_ed25519`). Set a -passphrase when prompted — it protects the key if your machine is -compromised. - -### Add the key to the SSH agent - -```text -$ eval "$(ssh-agent -s)" # start the agent -$ ssh-add ~/.ssh/id_ed25519 # add your key -``` - -On Windows (Git Bash), use the same commands. On macOS, add -`--apple-use-keychain` to avoid re-entering the passphrase. - -### Add the public key to GitHub - -```text -$ cat ~/.ssh/id_ed25519.pub # copy this output -``` - -On GitHub: Settings → SSH and GPG keys → New SSH key → paste the -public key. - -### Test the connection - -```text -$ ssh -T git@github.com -Hi username! You've successfully authenticated... -``` - -### Switch a repository from HTTPS to SSH - -```text -$ git remote set-url origin git@github.com:user/repo.git -``` - -## 4. Git Clients - -| Client | Platform | Notes | -|--------|----------|-------| -| [GitHub Desktop](https://desktop.github.com/) | Windows, macOS | Simple, GitHub-focused | -| [Sourcetree](https://www.sourcetreeapp.com/) | Windows, macOS | Full-featured, free | -| [GitKraken](https://www.gitkraken.com/) | Windows, macOS, Linux | Visual, cross-platform | -| [TortoiseGit](https://tortoisegit.org/) | Windows | Shell integration | -| [Git Extensions](https://gitextensions.github.io/) | Windows | Lightweight, open source | - -## 5. References - -### Books and tutorials - -- [Pro Git Book](http://git-scm.com/book/en/v2) — comprehensive, free, official -- [Git Internals PDF](https://github.com/pluralsight/git-internals-pdf/releases) — deep dive into Git's object model -- [Think Like a Git](http://think-like-a-git.net/) — mental models for Git -- [Git Immersion](https://gitimmersion.com/index.html) — guided hands-on tour - -### Troubleshooting - -- [Git Flight Rules](https://github.com/k88hudson/git-flight-rules) — what to do when things go wrong -- [Oh Shit, Git!?!](https://ohshitgit.com/) — common mistakes and fixes - -### Visualization - -- [Learn Git Branching](https://learngitbranching.js.org/) — interactive branching exercises -- [Visualizing Git](https://git-school.github.io/visualizing-git/) — real-time commit graph - -### Workflows - -- [A Successful Git Branching Model](https://nvie.com/posts/a-successful-git-branching-model/) — Git Flow (Vincent Driessen) -- [Trunk-Based Development](https://www.toptal.com/software/trunk-based-development-git-flow) — comparison with Git Flow -- [OneFlow](https://www.endoflineblog.com/oneflow-a-git-branching-model-and-workflow) — simplified alternative to Git Flow - -## 6. Notes - -1. Git cannot commit empty folders. Add a placeholder file (e.g. - `.gitkeep`) if you need an empty directory tracked. - -2. Unlike `svn add`, `git add` does not permanently track a file — it - stages changes for the next commit. Run `git add` each time a file - is modified. - -3. Changing the user email in configuration causes future commits to - appear under a different identity. Past commits are not affected. diff --git a/astro-site/src/content/docs/branching-and-merging.md b/astro-site/src/content/docs/branching-and-merging.md deleted file mode 100644 index c7f5ac5..0000000 --- a/astro-site/src/content/docs/branching-and-merging.md +++ /dev/null @@ -1,643 +0,0 @@ ---- -title: "Branching and Merging" -section: "branching-and-merging" -order: 3 ---- - -## 1. Overview - -This chapter covers the daily workflow of parallel development in Git — -creating branches, combining work through merging and rebasing, -resolving conflicts, and temporarily shelving changes with the stash. -These are the operations you will use most often when working with others -or managing multiple features at once. - -In this chapter you will learn: - -- How to create, rename, and delete branches -- Merge strategies: fast-forward, 3-way, no-fast-forward, and squash -- How rebasing produces a linear history -- How to cherry-pick individual commits between branches -- How to resolve merge and rebase conflicts -- How to stash and restore work in progress - -## 2. Branching - -As covered in [Building Blocks](../building-blocks/), a branch is a -pointer to a specific commit. Creating a branch is a "cheap" operation — -Git does not copy any files, it only creates a new reference. This -section focuses on how branches are used in practice. - -### Branch workflow - -Here is an example workflow showing how branches are created, used, and -merged. - -**1. Initial state** — a linear history with three commits: - -![Initial repo](../assets/images/git-branch-before.png) - -**2. Create a branch** — a new `bugfix` branch is created at the same -commit. Both branches point to commit #3: - -![New branch](../assets/images/git-branch-new.png) - -```text -$ git branch bugfix -$ git switch bugfix -``` - -The two commands above can be combined into one: - -```text -$ git switch -c bugfix -``` - -The `-c` flag creates the branch and switches to it in a single step. - -**3. Commit on the new branch** — commit #4 is added on `bugfix`. -`main` stays at commit #3: - -![Change bugfix](../assets/images/git-branch-change-bugfix.png) - -**4. Commit on main** — switching back to `main` and committing creates -a divergence. Both branches now have commits the other doesn't: - -![Change main](../assets/images/git-branch-change-main.png) - -```text -$ git switch main -# ... make changes ... -$ git commit -m "Update on main" -``` - -**5. Merge** — merging `bugfix` into `main` creates a merge commit (#6) -that combines both lines of work: - -![Merge branches](../assets/images/git-branch-merge.png) - -```text -$ git merge bugfix -``` - -After merging, the `bugfix` branch can be safely deleted — its changes -are now part of `main`. - -### Deleting branches - -Deleting a branch removes only the named reference, not the commits. - -```text -git branch -d # safe — only works if merged -git branch -D # force — deletes even if unmerged -``` - -With `-D`, unmerged commits become orphaned and will be cleaned up by -Git's garbage collector. Until then, the branch can be restored. - -### Renaming branches - -The current branch can be renamed using: -```text -git branch -m -``` - -To rename a different branch: -```text -git branch -m -``` - -## 3. Merging - -Merging is a process of combining changes from different branches. Usually -this is required when people are working in parallel on the same source code. -The file versions in each branch are compared and analyzed line by line. - -```text -$ git switch main # switch to the branch you want to merge into -$ git merge feature # merge "feature" into "main" -``` - -![Merge concept](../assets/images/git-merge-concept.png) - -Git chooses the merge strategy automatically based on the branch history. - -### Fast-forward - -When the target branch has no new commits since the source branch was -created, Git simply moves the target branch tip forward to the latest -commit on the source branch. No merge commit is created. - -![Merge fast-forward](../assets/images/git-merge-fast-forward.png) - -### 3-Way merge - -When both branches have diverged with new commits, Git will analyze the -files to determine how to combine the differences. The 3-way merge algorithm -uses a common ancestor and the two branch tips to perform the analysis. - -It looks for sections which are the same in two of the three revisions. This -indicates that the third revision is unique and the section will be added to -the merge result. Sections that are different in all three revisions are -marked as a conflict situation and left for the user to resolve. - -![3-way merge concept](../assets/images/git-merge-3-way-concept.png) - -### Forcing a merge commit - -Even when a fast-forward is possible, you can force Git to create a merge -commit with `--no-ff`: - -```text -$ git merge --no-ff feature -``` - -This preserves the branch structure in history, making it clear that a -set of commits came from a feature branch. Many teams require `--no-ff` -merges so that the history shows when work was branched and integrated. - -### Squash merge - -A squash merge combines all commits from the source branch into a single -change set but does not create a merge commit: - -```text -$ git merge --squash feature -$ git commit -m "Add feature X" -``` - -The result is a single commit on the target branch containing all the -changes. The source branch history is not linked — Git does not record -that a merge happened, so you must delete the source branch manually -afterward. - -Squash merges are useful when a feature branch contains many small or -messy commits and you want a clean, single-commit result on `main`. - -## 4. Rebasing - -Rebasing is an **alternative to merging**. Instead of creating a merge -commit, it replays your branch's commits on top of another branch, -producing a linear history. - -```text -$ git switch feature # switch to the branch you want to rebase -$ git rebase main # replay feature commits on top of main -``` - -![Rebase](../assets/images/git-merge-rebase.png) - -The replayed commits (5', 6') are **new commits** — they have the same -changes as the originals but different hashes because their parent -changed. The original commits become orphaned. - -> **Warning:** Rebasing **must** be used only on local (unpushed) -> history. Rebasing shared commits rewrites their hashes and causes -> conflicts for anyone who already has the originals. - -The table below summarizes when to use each approach: - -| | Merge | Rebase | -|---|---|---| -| History | Preserves the branch structure (non-linear) | Produces a straight line (linear) | -| Creates | A merge commit with two parents | New copies of each commit | -| Safe on shared branches? | Yes | No — rewrites commit hashes | -| When to use | Combining finished work | Cleaning up local history before merging | - -## 5. Cherry-picking - -Cherry-picking copies a single commit from one branch onto another. Unlike -merging, it does not bring over the full branch history — only the changes -from the selected commit. - -```text -$ git cherry-pick abc1234 -``` - -Git creates a **new commit** on the current branch with the same changes -as `abc1234` but a different hash. The original commit remains on its -branch untouched. - -![Cherry-pick](../assets/images/git-cherry-pick.png) - -Common use cases: - - - **Backporting a fix** — applying a bug fix from `main` to a release - branch without merging all of `main` - - **Selective integration** — pulling one useful commit from a feature - branch that is not ready to merge in full - -> **Caution:** Cherry-picked commits are duplicates — the same change -> exists in two places with different hashes. If the source branch is -> later merged, Git usually handles this cleanly, but it can produce -> confusing history. Prefer merging when possible. - -## 6. Conflicts - -A merge conflict occurs when Git cannot automatically combine changes from -two branches. This happens when both branches modify the same lines in the -same file, or when one branch deletes a file that the other branch modifies. -Git stops the merge and asks the user to resolve the conflict manually. - -Conflicts are a normal part of collaborative development. They do not -indicate an error — they simply mean that Git needs human judgement to -decide which changes to keep. - -### When conflicts occur - -Conflicts can arise during any operation that combines work from -different sources: - -| Operation | When it conflicts | -|-----------|------------------| -| `git merge` | Both branches changed the same lines | -| `git rebase` | A replayed commit touches lines modified upstream | -| `git cherry-pick` | The picked commit overlaps with the current state | -| `git pull` | Remote changes overlap with local changes (see [Remote Repositories](../remote-repositories/)) | -| `git stash pop` | Stashed changes conflict with the current working tree | - -### How Git marks conflicts - -When Git detects a conflict it inserts conflict markers directly into the -affected file. The markers divide the conflicting sections into two parts: - -```text -<<<<<<< HEAD -This is the content from the current branch (ours). -======= -This is the content from the incoming branch (theirs). ->>>>>>> feature-branch -``` - -| Marker | Meaning | -|--------|---------| -| `<<<<<<< HEAD` | Start of the current branch content | -| `=======` | Separator between the two versions | -| `>>>>>>> feature-branch` | End of the incoming branch content | - -The text between `<<<<<<< HEAD` and `=======` is what exists on the -current branch. The text between `=======` and `>>>>>>>` is what the -incoming branch wants to introduce. The label after `>>>>>>>` shows the -name of the branch or commit being merged in. - -A single file can contain multiple conflict blocks if several regions -of the file were changed by both branches. - -### Conflicts during merge - -Resolving a merge conflict follows a predictable sequence: - -![Conflict resolution workflow](../assets/images/git-conflict-workflow.png) - -```text -$ git merge feature # 1. Git stops and reports conflicts -$ git status # 2. Identify the conflicting files -# ... edit each file, remove markers, keep/combine/rewrite content ... -$ git add src/config.yaml # 3. Stage each resolved file -$ git commit # 4. Complete the merge -``` - -If the conflict is too complex or the merge was started by mistake, -abort and return to the pre-merge state: - -```text -$ git merge --abort -``` - -### Conflicts during rebase - -Rebasing replays commits one at a time, so conflicts can occur at each -step. The same resolve-stage cycle repeats for every conflicting commit: - -![Rebase conflict workflow](../assets/images/git-conflict-rebase-workflow.png) - -```text -$ git rebase main # 1. Git stops at the first conflict -# ... edit the file, remove markers ... -$ git add src/main.py # 2. Stage the resolved file -$ git rebase --continue # 3. Replay the next commit -# ... repeat 1-3 if more conflicts arise ... -``` - -To abandon the rebase and return to the original state: - -```text -$ git rebase --abort -``` - -To skip the current conflicting commit (dropping its changes): - -```text -$ git rebase --skip -``` - -### Using merge tools - -Instead of editing conflict markers by hand, you can use a graphical -merge tool. Most tools show three panes (base, ours, theirs) alongside -a result pane. - -```text -git mergetool # launch the configured tool -git config --global merge.tool meld # set a default (meld, kdiff3, VS Code, etc.) -git config --global mergetool.keepBackup false # disable .orig backup files -``` - -### Preventing conflicts - -Conflicts cannot be eliminated, but a few habits keep them small and -rare: - -| Habit | Why it helps | -|-------|-------------| -| Keep branches short-lived | Less divergence means fewer overlapping changes | -| Pull frequently | Staying close to `main` catches conflicts early | -| Make small, focused commits | Smaller changes produce simpler conflicts | -| Coordinate with teammates | Knowing who edits which files avoids surprises | -| Avoid whole-file reformatting | Style-only changes conflict with every other branch | - -### Practical example - -Two developers work on the same file. Alice changes a greeting on the -`main` branch, and Bob changes it on a `feature` branch. - -Initial file (`greeting.txt`) on both branches: - -```text -Hello, welcome to the project. -``` - -Alice changes it on `main`: - -```text -Hello, welcome to the project! We are glad you are here. -``` - -Bob changes it on `feature`: - -```text -Hi there, welcome to the project. -``` - -When Bob merges `main` into his branch, Git produces a conflict: - -```text -$ git merge main -Auto-merging greeting.txt -CONFLICT (content): Merge conflict in greeting.txt -Automatic merge failed; fix conflicts and then commit the result. -``` - -The file now contains: - -```text -<<<<<<< HEAD -Hi there, welcome to the project. -======= -Hello, welcome to the project! We are glad you are here. ->>>>>>> main -``` - -Bob decides to combine both changes: - -```text -Hi there, welcome to the project! We are glad you are here. -``` - -He removes all conflict markers, stages the file, and completes the merge: - -```text -git add greeting.txt -git commit -m "Merge main into feature, combine greeting changes" -``` - -The conflict is resolved and the repository history records the merge. - -## 7. Stashing - -The stash allows changes to be saved without committing broken or untested -code before switching to another branch. - -![Stash workflow](../assets/images/git-stash.png) - -### When to use the stash - -You are working on a feature branch and need to switch to `main` for an -urgent fix, but your changes are not ready to commit. The stash saves -your working tree and index state, restores a clean working tree, and -lets you come back later. - -### How it works internally - -Git does not use a separate storage mechanism for stashes — it reuses -the same commit objects that power the rest of the repository. - -![Stash internals](../assets/images/git-stash-internals.png) - -When you stash, your work may exist in two places: files you have -edited but not staged (working tree), and files you have staged with -`git add` but not committed (index). Git preserves both separately so -that when you restore, staged files return to the index and unstaged -files return to the working tree. - -A stash is a commit object **W** with two parents: - -| Commit | Contains | Points to | -|--------|----------|-----------| -| **W** | Working tree changes | HEAD and I | -| **I** | Staged changes | HEAD | -| **U** | Untracked files (only with `-u`) | — | - -`git stash pop` restores both snapshots and removes the stash entry. -`git stash apply` does the same but keeps the entry for later reuse. - -## Exercises - -All exercises use the `concepts-lab` repository created in -[Building Blocks](../building-blocks/). If you skipped that chapter, create a new -repository with at least one commit before starting. - -### Exercise 1: Branch Lifecycle - -**Task:** Practice creating, using, merging, and deleting a feature branch. - -**Steps:** - -1. In `concepts-lab`, confirm you are on `main` with `git branch` -2. Create and switch to a branch called `feature/greeting` using `git branch feature/greeting` then `git switch feature/greeting` -3. Create a file `greeting.txt` with the text `Hello from feature branch` -4. Stage and commit with the message `Add greeting` -5. Run `git log --oneline --all` to see both branches -6. Switch back to `main` — confirm `greeting.txt` does not exist on `main` -7. Merge the feature branch with `git merge feature/greeting` -8. Confirm `greeting.txt` now exists on `main` -9. Delete the feature branch with `git branch -d feature/greeting` -10. Run `git branch` to confirm only `main` remains - -**Verify:** - -`git log --oneline --graph` shows the merge. `greeting.txt` exists on `main`. `git branch` lists only `main`. - -### Exercise 2: Three-Way Merge with a Conflict - -**Task:** Create a merge conflict, inspect the conflict markers, and resolve it manually. - -**Steps:** - -1. In `concepts-lab`, create a file `config.txt` with the line `mode=production` and commit it on `main` -2. Create and switch to a branch `feature/debug` using `git switch -c feature/debug` -3. Change the line in `config.txt` to `mode=debug` and commit -4. Switch back to `main` -5. Change the same line in `config.txt` to `mode=staging` and commit -6. Run `git merge feature/debug` -7. Open `config.txt` and locate the conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) -8. Run `git ls-files --stage` and observe the three stage entries (base, ours, theirs) for `config.txt` -9. Edit `config.txt` to resolve the conflict by choosing one value or combining them -10. Stage the resolved file with `git add config.txt` and complete the merge with `git commit` - -**Verify:** - -After the merge commit, `git log --oneline --graph` shows the two branches converging. `git ls-files --stage` shows a single stage-0 entry for `config.txt`. The file contains the resolved content with no conflict markers. - -### Exercise 3: No-fast-forward and squash merge - -**Task:** Compare `--no-ff` and `--squash` merge strategies. - -**Steps:** - -1. In `concepts-lab`, create and switch to `feature/noff` -2. Create a file `noff.txt`, stage and commit with the message `Add noff` -3. Switch to `main` and run `git merge --no-ff feature/noff` -4. Run `git log --oneline --graph` — note the merge commit even though fast-forward was possible -5. Create and switch to `feature/squash` -6. Create two files `squash1.txt` and `squash2.txt`, commit each separately -7. Switch to `main` and run `git merge --squash feature/squash` -8. Run `git status` — the changes are staged but not committed -9. Commit with the message `Add squash files` -10. Run `git log --oneline --graph` — note the single commit with no merge history - -**Verify:** - -The `--no-ff` merge shows a merge commit with two parents. The `--squash` merge shows a single flat commit. `git branch -d feature/squash` warns the branch is not fully merged because Git did not record a merge relationship. - -### Exercise 4: Cherry-pick a commit - -**Task:** Copy a single commit from one branch to another using cherry-pick. - -**Steps:** - -1. In `concepts-lab`, create and switch to `feature/cherry` -2. Create a file `fix.txt` with the content `Bug fix`, commit with the message `Fix bug` -3. Create a file `wip.txt` with the content `Not ready`, commit with the message `Work in progress` -4. Run `git log --oneline` and note the hash of the `Fix bug` commit -5. Switch to `main` -6. Run `git cherry-pick ` using the hash from step 4 -7. Run `git log --oneline` on `main` — the fix appears as a new commit -8. Confirm `fix.txt` exists on `main` but `wip.txt` does not - -**Verify:** - -`git log --oneline` on `main` shows the cherry-picked commit with a different hash than the original. `fix.txt` exists, `wip.txt` does not. The `feature/cherry` branch is unchanged. - -### Exercise 5: Rebase a feature branch - -**Task:** Rebase a feature branch onto `main` to produce a linear history. - -**Steps:** - -1. In `concepts-lab`, create and switch to `feature/rebase` -2. Create a file `rebase1.txt`, commit with the message `Add rebase1` -3. Create a file `rebase2.txt`, commit with the message `Add rebase2` -4. Switch to `main` and create a file `main-update.txt`, commit with the message `Update main` -5. Run `git log --oneline --all --graph` — note the divergence -6. Switch to `feature/rebase` -7. Run `git rebase main` -8. Run `git log --oneline --all --graph` — note the linear history -9. Check that `main-update.txt` exists on `feature/rebase` -10. Run `git log --oneline` and compare the hashes of your two feature commits with the originals from step 5 - -**Verify:** - -The graph shows a straight line — no fork. The feature commits have new hashes because their parent changed. `main-update.txt` is present on the feature branch. - -### Exercise 6: Stash and restore work in progress - -**Task:** Use the stash to save uncommitted changes, switch branches, then restore them. - -**Steps:** - -1. In `concepts-lab`, make sure you are on `main` with a clean working tree -2. Create a file `notes.txt` with the content `Work in progress` -3. Stage the file with `git add notes.txt` -4. Run `git stash push -m "wip: notes"` to save the changes -5. Confirm the working tree is clean with `git status` -6. Run `git stash list` and note the stash entry `stash@{0}` -7. Inspect the stash reference at `.git/refs/stash` and run `git cat-file -p` on it -8. Create and switch to a new branch `feature/other`, make any commit, then switch back to `main` -9. Restore the stashed changes with `git stash pop` -10. Confirm `notes.txt` is back in the working tree and staged - -**Verify:** - -After `git stash pop`, `git status` shows `notes.txt` as a staged new file. `git stash list` is empty. - -## Quiz - -**Q1.** What happens during a fast-forward merge? - -- A) Git creates a new merge commit with two parents -- B) Git copies files from one branch to another -- C) Git moves the target branch tip forward to the source branch tip — no merge commit is created -- D) Git rebases the source branch onto the target branch - -**Q2.** In a 3-way merge, what are the three revisions Git compares? - -- A) HEAD, the index, and the working tree -- B) The first commit, the last commit, and the tag -- C) The local, global, and system configurations -- D) The common ancestor, the current branch tip, and the incoming branch tip - -**Q3.** What does `git merge --abort` do? - -- A) Deletes the branch that caused the conflict -- B) Commits the merge with conflict markers still in the files -- C) Restores the repository to the state before the merge began -- D) Reverts the last successful merge - -**Q4.** Why should you avoid rebasing commits that have already been pushed? - -- A) Rebase deletes the commits permanently -- B) Rebase only works on the main branch -- C) Rebase cannot handle merge conflicts -- D) Rebase rewrites commit hashes, causing conflicts for anyone who already has the originals - -**Q5.** What does `git merge --no-ff` do? - -- A) Prevents the merge from happening -- B) Creates a merge commit even when a fast-forward is possible -- C) Merges without resolving conflicts -- D) Deletes the source branch after merging - -**Q6.** What does `git cherry-pick` do? - -- A) Merges an entire branch into the current branch -- B) Copies a single commit onto the current branch with a new hash -- C) Moves a commit from one branch to another, removing it from the source -- D) Reverts a specific commit - -**Q7.** What does a stash store internally? - -- A) A patch file in `.git/stash/` -- B) A diff of the working tree only -- C) Commit objects for the working tree, index, and optionally untracked files -- D) A compressed archive of the entire repository - -### Answers - -1. C — Git moves the target branch tip forward to the source branch tip — no merge commit is created -2. D — The common ancestor, the current branch tip, and the incoming branch tip -3. C — Restores the repository to the state before the merge began -4. D — Rebase rewrites commit hashes, causing conflicts for anyone who already has the originals -5. B — Creates a merge commit even when a fast-forward is possible -6. B — Copies a single commit onto the current branch with a new hash -7. C — Commit objects for the working tree, index, and optionally untracked files diff --git a/astro-site/src/content/docs/building-blocks.md b/astro-site/src/content/docs/building-blocks.md deleted file mode 100644 index 0359193..0000000 --- a/astro-site/src/content/docs/building-blocks.md +++ /dev/null @@ -1,841 +0,0 @@ ---- -title: "Building Blocks" -section: "building-blocks" -order: 2 ---- - -## 1. Overview - -This chapter covers the internal building blocks of Git — how repositories -are structured, how Git stores data as objects, and how references connect -everything together. Understanding these internals will help you make sense -of what Git commands actually do under the hood. - -In this chapter you will learn: - -- The difference between bare and non-bare repositories -- How Git's object model stores files, directories, commits, and tags -- How the index (staging area) prepares changes for commits -- How references and HEAD connect names to commits -- How to navigate and rewrite history with reset - -## 2. Repository - -A repository is a database that stores the complete history of a project -as a series of snapshots (commits). Every commit records exactly what -every tracked file looked like at that moment — Git stores full snapshots, -not differences between versions. - -The repository lives inside a hidden folder called `.git`. This folder -**is** the repository — it contains all objects (files, directories, -commits), all references (branches, tags), and the project configuration. -Everything outside `.git` is the **working tree** — the files you edit -directly. - -A repository can be **local** (on your machine) or **remote** (on a -server like GitHub). Git treats both as equals — you can push to and -pull from any repository you have access to. - -Git supports two repository layouts: - -### Bare Repository - -A **bare repository** is a Git repository without a working tree — it -contains only the `.git` internals (objects, refs, config) and no -checked-out files. Hosting services like GitHub and GitLab store repositories as bare -on the server. When you edit a file through GitHub's web interface, -GitHub creates a commit directly — it does not use a working tree. - -> **Note:** "bare" and "remote" are not the same thing. A remote is any -> repository you connect to via URL. Remotes are *usually* bare, but a -> remote can also be a regular repository on another machine. - -```text -git init --bare project.git - -PROJECT.GIT/ -├───hooks # Scripts that run on events (pre-commit, post-merge, etc.) -├───info # Repository metadata (exclude patterns, etc.) -├───objects # All Git objects (blobs, trees, commits, tags) -│ ├───info # Object storage metadata -│ └───pack # Compressed object packs for efficiency -└───refs # Named references to commits - ├───heads # Branch tips - └───tags # Tag references -``` - -### Non-bare Repository - -A **non-bare repository** (also called a regular or working repository) -is what you get when you clone or run `git init`. It has a working tree -where you create, edit and delete files, plus a hidden `.git` folder -that stores the full history and configuration. - -```text -git clone project.git - -PROJECT/ -│ readme.md # Working tree — your editable files -└───.git # Repository internals (same as bare) - ├───hooks # Event scripts - ├───info # Repository metadata - ├───objects # All Git objects - │ ├───info # Object storage metadata - │ └───pack # Compressed object packs - └───refs # Named references - ├───heads # Branch tips - └───tags # Tag references -``` - -The `.git` folder contains the same structure as a bare repository. -The difference is that a non-bare repository also has a working tree -next to it — the place where you do your actual work. - -## 3. Object Model - -Every time you commit, Git takes a **snapshot** — a complete picture of -every tracked file in your project at that moment. A snapshot is not a -list of what changed since the last commit; it is a full record of what -every file looks like right now. If a file has not changed, Git does not -store a new copy — it reuses the existing one. This makes snapshots -efficient despite recording the entire project each time. - -Git stores these snapshots — along with files, directories, and labels — -as **objects**. There are four object types, and every object in Git is -one of these: - -| Type | Stores | Analogy | -|------|--------|---------| -| Blob | The contents of a single file | A page in a notebook | -| Tree | A directory listing (references to blobs and other trees) | A table of contents | -| Commit | A snapshot of the project (references a tree) + metadata | A dated entry in a logbook | -| Tag | A named label (references a commit) + metadata | A sticky note on a logbook entry | - -### Blob Object - -A blob (**b**inary **l**arge **ob**ject) stores the contents of a single -file — the actual text or binary data inside it. It contains only the -raw data — no file name, no path, no permissions. Two files with -identical content share the same blob, even if they have different names. - -```text -$ git cat-file -p b24d71e -# Git Tutorial - -Welcome to the Git tutorial. This guide covers the fundamentals -of version control with Git. -``` - -A blob is created when you run `git add`. Git compresses the file -contents (makes them smaller for storage), computes a hash (a unique -identifier — explained below), and stores the result as an object. - -### Tree Object - -A tree represents a directory (folder). It contains a list of entries, -where each entry is a reference to either a blob (file) or another tree -(subfolder), along with the file name and permissions (who is allowed -to read, write, or execute the file). - -```text -$ git cat-file -p da8d6f3 -100644 blob b24d71e... .gitignore -040000 tree 6e4aac2... src/ -040000 tree 34d3238... docs/ -100644 blob 676b59c... README.md -``` - -Each entry starts with a **mode** that encodes the file type and -permissions: - -| Mode | Meaning | -|------|---------| -| `100644` | Regular file — most files you create will have this mode | -| `100755` | Executable file — a program or script that can be run directly | -| `040000` | Subdirectory — points to another tree object | -| `120000` | Symbolic link — a special file that contains a path to another file; opening it opens the target instead | -| `160000` | Submodule — a reference to a commit in another Git repository | - -Together, the tree objects form a hierarchy that mirrors your project's -folder structure at a specific point in time. - -### Commit Object - -A commit records a snapshot of the entire project at a specific moment. -It references a single root tree (the top-level directory of the project) -and stores metadata — extra information about the change: who made it -(author), when (timestamp), and a short message describing why. - -```text -$ git cat-file -p 3a7ed53 -tree da8d6f364612a07419ba0baf35dced6b52948c4f -parent 1f8716a405a8c09ef92012e713d3c087ae0b2678 -author user 1641905621 +0200 -committer user 1641905621 +0200 - -Add project documentation -``` - -Every commit (except the very first one) has a **parent** — the commit -that came before it. This parent reference forms a chain that is the -history of your project. Merge commits have two or more parents. - -### Tag Object (Labels) - -A tag object points to a commit and records extra information: who -created the tag, when, and a message explaining what it represents. -Tags are used to mark important points in the project history, most -commonly release versions (e.g. `v1.0`, `v2.0`). - -Because tag objects carry this extra information, they are called -**annotated tags** — the annotation is the metadata that makes them -more than just a name. - -```text -$ git cat-file -p v1.0 -object 3a7ed539ea18da12d5707001d7a4c176f8911240 -type commit -tag v1.0 -tagger user 1641911532 +0200 - -First stable release -``` - -Unlike branches, tags do not move — they always point to the same -commit. Use annotated tags for anything you plan to share with others. -Tags are **not** pushed automatically — you must run -`git push origin --tags` or `git push origin v1.0` explicitly. - -> **Note:** Git also supports **lightweight tags**, which are not -> objects. A lightweight tag is just a plain file in `.git/refs/tags/` -> containing a commit hash — no author, no date, no message. They are -> quick to create but carry no information about who created them or -> why. Use lightweight tags for temporary local bookmarks only. - -### How Objects Relate - -The diagram below shows how the four object types connect. A tag -points to a commit, a commit points to a root tree, and a tree -contains blobs (files) and other trees (subdirectories). All objects -are immutable and stored in `.git/objects/`. - -![Object Model](../assets/images/git-object-model.png) - -Here is a concrete example showing two commits with their trees and -blobs. Each commit is a full snapshot — commit N has four files, -commit M has two: - -``` - tag "v1.0" - │ - ▼ - commit N ──parent──▶ commit M ──▶ ... - │ │ - ▼ ▼ - tree (root) tree (root) - ├── blob README.md ├── blob README.md - ├── tree src/ └── blob .gitignore - │ └── blob main.py - └── blob .gitignore -``` - -Following these references from any commit, Git can reconstruct the -exact state of every file at that point in time. - -### How Objects Are Identified - -Every object gets a unique 40-character identifier called a **hash**. -Think of it like a fingerprint — just as no two people have the same -fingerprint, no two different pieces of content produce the same hash. - -Git computes the hash using an algorithm called -[SHA-1](https://en.wikipedia.org/wiki/SHA-1). The algorithm takes the -object's content as input and produces a fixed-length string of letters -and numbers. The same content always produces the same hash. If even one -character changes, the hash changes completely. - -```text -$ git log -1 -commit db79ba36b521373fcfaff3c2e422326a59fe26f6 (HEAD -> main) -Author: Your Name -Date: Sun Jan 9 20:05:15 2022 +0200 -``` - -The value `db79ba36b521373fcfaff3c2e422326a59fe26f6` is the hash of this -commit object. In practice, you only need the first 7–8 characters -(e.g. `db79ba3`) — Git will resolve the full hash automatically. - -### How Objects Are Stored - -Git stores objects as compressed files in `.git/objects/`. To avoid -putting thousands of files in a single folder, Git uses the first two -characters of the hash as a subfolder name and the remaining 38 -characters as the file name. - -For example, an object with the hash `3a7ed539ea18da12d5707001d7a4c176f8911240` -is stored in a folder named `3a` with a file named after the remaining -38 characters: - -```text -$ ls .git/objects/3a/ - -.git/objects/ -│ -├───3a/ ← first 2 characters of the hash -│ 7ed539ea18da12d5707001d7a4c176f8911240 ← remaining 38 characters -│ f20b1c84e9a7d3561e0c42f890abcde1234567 ← another object starting with 3a -│ 01cc9d8b7e6f5432a1b0c9d8e7f6a5b4c3d2e1 ← and another -│ -├───info/ -└───pack/ -``` - -Every object whose hash starts with the same two characters ends up in -the same folder. Git splits objects across 256 possible folders (`00` -to `ff`) to keep each folder manageable. - -As a repository grows further, Git compresses multiple objects into a -single file in the `pack/` directory for efficiency. - -### Inspecting Objects - -You will rarely need to inspect objects directly, but it is useful for -understanding how Git works under the hood or for debugging unexpected -behaviour. The `git cat-file` command lets you examine any object by -its hash (you only need the first 7–8 characters): - -```text -$ git cat-file -t 3a7ed53 # what type of object is this? -commit - -$ git cat-file -s 3a7ed53 # how large is this object? -231 - -$ git cat-file -p 3a7ed53 # show the full content -tree da8d6f364612a07419ba0baf35dced6b52948c4f -parent 1f8716a405a8c09ef92012e713d3c087ae0b2678 -author user 1641905621 +0200 -committer user 1641905621 +0200 - -Add project documentation -``` - -| Flag | Shows | Example output | -|------|-------|----------------| -| `-t` | Object type | `commit`, `tree`, `blob`, or `tag` | -| `-s` | Object size in bytes | `231` | -| `-p` | Object content (pretty-print) | Depends on the type — see examples above | - -## 4. Index (Staging Area) - -The index is the area between your working tree and the repository. It -holds a list of changes that are ready to be included in the next -commit. In chapter 01 we called this the *staging area* — the index is -the internal name for the same thing. - -When you edit a file, the change exists only in your working tree. When -you run `git add`, Git copies the change into the index. When you run -`git commit`, Git takes everything in the index and saves it as a new -commit. This two-step process lets you choose exactly which changes to -include in each commit, even if you modified many files. - -``` -Working tree ──git add──▶ Index ──git commit──▶ Repository -``` - -### What the Index Contains - -Internally, the index is a binary file at `.git/index`. It stores a -sorted list of every tracked file with three pieces of information: -the file mode (type and permissions), the blob hash (which object holds -the file contents), and the file path. - -You can inspect the index with `git ls-files --stage`: - -```text -$ git ls-files --stage -100644 63c918c667fa005ff12ad89437f2fdc80926e21c 0 .gitignore -100644 5529b198e8d14decbe4ad99db3f7fb632de0439d 0 src/main.py -``` - -Each line shows: ` `. The -stage flag is `0` during normal operation — it changes to `1`, `2`, or -`3` only during a merge conflict (covered in -[Branching and Merging](../branching-and-merging/)). - -### Understanding the Mode Field - -The mode is a six-digit octal number split into two groups: - -``` -100 644 -─┬─ ─┬─ - │ └── permissions (644 = owner can read/write, others can read) - └─────── file type (100 = regular file) -``` - -The first three digits identify the **type** of entry: - -| Type code | Meaning | -|-----------|---------| -| `100` | Regular file — most files you create will have this type | -| `040` | Directory — points to another tree object | -| `120` | Symbolic link — a special file that contains a path to another file; opening it opens the target instead | -| `160` | Submodule — a reference to a commit in another Git repository | - -The last three digits are the **permissions**, using the same octal -notation as Unix file systems. Each digit represents a group of users -(owner, group, others), and each digit is a sum of: read (4), write (2), -and execute (1). - -| Permission | Meaning | -|------------|---------| -| `644` | Owner can read and write; everyone else can only read | -| `755` | Owner can read, write, and execute; everyone else can read and execute | - -So `100644` means "regular file, readable and writable by the owner" -and `100755` means "executable file, runnable by anyone." - -## 5. References - -Every object in Git is identified by a 40-character hash, but humans -don't think in hashes — we think in names like `main`, `feature/login`, -or `v1.0`. A **reference** (or **ref**) is a file that maps a -human-readable name to a commit hash. References are what make Git -usable. - -### Where References Live - -References are stored as small text files inside `.git/refs/`, with one -exception — HEAD lives directly at `.git/HEAD`. Each reference file -contains a single commit hash: - -``` -.git/ -│ HEAD # Points to the current branch (or commit) -│ -└───refs/ - ├───heads/ # Branch references - │ main # Contains the hash of the latest commit on main - │ feature/login # Contains the hash of the latest commit on this branch - │ - ├───remotes/ # Remote-tracking references - │ └───origin/ - │ main # Last known position of origin/main - │ - └───tags/ # Tag references - v1.0 # Points to a tag object or directly to a commit -``` - -For example, reading the `main` branch reference shows the hash of the -commit at the tip of that branch: - -```text -$ cat .git/refs/heads/main -3002ad0adb4c6b24caea57b5f0e4be0b09de89af -``` - -### HEAD — The Current Position - -HEAD is a special reference stored at `.git/HEAD`. It tells Git which -branch or commit you are currently working on. - -**Attached HEAD** — the normal state. HEAD points to a branch name, -and that branch points to a commit. When you make a new commit, the -branch moves forward and HEAD follows automatically: - -```text -$ cat .git/HEAD -ref: refs/heads/main -``` - -**Detached HEAD** — HEAD points directly to a commit instead of a -branch. This happens when you check out a specific commit or tag. -You can look around and make experimental changes, but any new commits -will not belong to any branch and can be lost if you switch away: - -```text -$ git checkout 3002ad0 -$ cat .git/HEAD -3002ad0adb4c6b24caea57b5f0e4be0b09de89af -``` - -### Branches vs Tags - -Both branches and tags are references — files containing a commit hash. -The difference is how they behave: - -| | Branch | Tag | -|---|---|---| -| Stored in | `.git/refs/heads/` | `.git/refs/tags/` | -| Moves? | Yes — advances to the new commit on each commit | No — always points to the same commit | -| Purpose | Track the latest work on a line of development | Mark a specific point in history (e.g. a release) | - -### Remote-Tracking References - -When you run `git fetch`, Git downloads new commits from the remote -and updates the references in `.git/refs/remotes/`. These are -**remote-tracking references** — they record where each branch was on -the remote the last time you communicated with it. - -```text -$ cat .git/refs/remotes/origin/main -3002ad0adb4c6b24caea57b5f0e4be0b09de89af -``` - -If this hash matches your local `refs/heads/main`, the branches are in -sync. If they differ, one side has commits the other doesn't. - -## 6. History Navigation - -Navigating history in Git means moving two things: **HEAD** (your -current position) and **branch tips** (where each branch points). -Different commands move one or both of these. - -### Creating a New Branch Tip - -Creates a new branch pointing to a specific commit. HEAD does not -move — you stay on your current branch. - -**When you need this:** -- Starting work on a new feature or bug fix at the current commit -- Creating a branch from a past commit to fix a bug in an older release - -``` - A ← B ← C (main, HEAD) - ↑ ↑ - fix feature -``` - -A new branch can point to the current commit (`feature` at C) or to a -past commit (`fix` at B). HEAD does not move in either case. - -```text -$ git branch feature # new branch at current HEAD -$ git branch fix abc1234 # new branch at a specific commit -``` - -You can also create and switch (moves HEAD) to the new branch in one step with -`git switch -c`: - -```text -$ git switch -c feature # create and switch to "feature" branch -``` - -### Moving HEAD to Another Branch - -The most common navigation. HEAD moves to the tip of a different -branch. The branch tips do not move. - -**When you need this:** -- Switching to a branch to continue working on it or test it -- Returning to `main` from a feature branch before starting a new task - -``` -before: A ← B ← C (main, HEAD) - ↖ - D ← E (feature) - -after: A ← B ← C (main) - ↖ - D ← E (feature, HEAD) -``` - -```text -$ git switch feature # move HEAD to the "feature" branch tip -$ git switch main # move HEAD back to "main" -``` - -### Moving HEAD to a Specific Commit - -HEAD detaches from its branch and moves directly to a commit. No -branch tip moves. - -**When you need this:** -- Inspecting what the project looked like at an older commit -- Testing whether a bug existed in a previous version -- Building or running a tagged release (e.g. `git switch --detach v1.0`) - -``` -before: A ← B ← C (main, HEAD) - -after: A ← B ← C (main) - ↑ - HEAD (detached) -``` - -```text -$ git switch --detach abc1234 # detach HEAD and move it to commit abc1234 -``` - -In this state, any new commits you make will not belong to any branch -and can be lost when you switch away. To keep them, create a branch -first. - -### Moving a Branch Tip Without Switching - -Force-moves an existing branch to a different commit. HEAD does not -move — you stay on your current branch. Only the target branch pointer -is repositioned. - -**When you need this:** -- Pointing a branch at a specific commit after a mistake -- Aligning a branch with another branch's tip without merging - -```text -$ git branch -f feature abc1234 # move "feature" tip to commit abc1234 -``` - -> **Warning:** Force-moving a branch can make commits unreachable. If -> no other branch or tag points to the old commits, they will eventually -> be removed by Git's garbage collection. - -### Moving HEAD and Branch Tip Forward - -When you make a commit, the current branch tip moves forward -automatically to point to the new commit. HEAD follows because it is -attached to the branch. This is how branches grow — each commit -advances the tip by one step. - -**When you need this:** -- Every time you run `git commit` — this is the normal way branches grow -- When you `git merge` and Git creates a merge commit -- When you `git pull` and new commits are added to your branch - -``` -before commit: A ← B ← C (main, HEAD) -after commit: A ← B ← C ← D (main, HEAD) -``` - -### Moving HEAD and Branch Tip Backward - -Moves HEAD **and** the current branch tip together back to an earlier -commit. This is how you undo commits. Depending on the mode, it can -also update the index and working tree. - -``` -before: A ← B ← C (main, HEAD) - -after: A ← B (main, HEAD) - ↖ - C (orphaned — still points to B, but no branch points to C) -``` - -In all three modes, the branch tip and HEAD move back and commit C -becomes orphaned. The difference is what happens to the **changes that -were in commit C** — they can stay staged, become unstaged, or be -discarded entirely: - -| Mode | Index | Working tree | `git status` shows | -|------|-------|-------------|-------------------| -| `--soft` | Unchanged | Unchanged | "Changes to be committed" (staged) | -| `--mixed` | Reset | Unchanged | "Changes not staged for commit" (unstaged) | -| `--hard` | Reset | Reset | Clean working tree — changes are gone | - -**`--soft`** — the commit is undone but your changes look as if you had -just run `git add` and are ready to commit again. Useful when you want -to rewrite the commit message or combine several commits into one. - -**`--mixed`** (default) — the commit is undone and the changes are -unstaged, as if you had edited the files but not yet run `git add`. -Useful when you want to restage changes selectively. - -**`--hard`** — the commit is undone and the changes are discarded from -both the index and the working tree. Your files on disk are overwritten -to match the target commit. - -```text -$ git reset --soft HEAD~1 # move back 1 commit, keep changes staged -$ git reset --mixed HEAD~1 # move back 1 commit, unstage changes (default) -$ git reset --hard HEAD~1 # move back 1 commit, discard all changes -``` - -> **Warning:** `--hard` permanently discards uncommitted work. Use with -> caution. Commit C still exists in the object database and can be -> recovered with `git reflog` until Git's garbage collection removes it. - -## Exercises - -### Exercise 1: Create and Inspect a Repository - -**Task:** Initialize a new Git repository and explore its internal structure. - -**Steps:** - -1. Create a new directory called `concepts-lab` and navigate into it -2. Run `git init` to create a repository -3. List the contents of the `.git` directory -4. Identify the `objects`, `refs/heads`, and `refs/tags` subdirectories -5. Read the `HEAD` file and note what it points to -6. Create a bare repository called `concepts-lab.git` in a sibling directory using `git init --bare` -7. Compare the directory structure of the bare repository with the `.git` folder - -**Verify:** - -The `.git` directory exists and contains `objects`, `refs`, `HEAD`, and `config`. The bare repository has the same internal structure but no working tree. - -### Exercise 2: Explore Git Objects - -**Task:** Create blobs, trees, and commits, then inspect them with plumbing commands. - -**Steps:** - -1. In `concepts-lab`, create a file called `hello.txt` with the content `Hello, Git!` -2. Stage the file with `git add hello.txt` -3. Run `git ls-files --stage` and note the blob hash next to the file name -4. Inspect the blob content using `git cat-file -p ` -5. Inspect the blob type using `git cat-file -t ` -6. Commit the file -7. Run `git log --format=raw -1` to see the commit object and note the tree hash -8. Inspect the tree using `git cat-file -p ` -9. Verify that the tree references the same blob hash from step 3 -10. Browse the `.git/objects` directory and locate the two-character subdirectory matching the first two characters of the blob hash - -**Verify:** - -`git cat-file -p` on the blob prints `Hello, Git!`. The tree object lists the blob hash with mode `100644` and file name `hello.txt`. The corresponding object file exists on disk under `.git/objects`. - -### Exercise 3: Lightweight and Annotated Tags - -**Task:** Create both tag types and compare how Git stores them internally. - -**Steps:** - -1. In `concepts-lab`, make sure you have at least one commit -2. Create a lightweight tag called `v0.1` using `git tag v0.1` -3. Create an annotated tag called `v1.0` with the message `First release` using `git tag -a v1.0 -m "First release"` -4. List all tags with `git tag` -5. Read the lightweight tag reference file at `.git/refs/tags/v0.1` and note the hash -6. Read the annotated tag reference file at `.git/refs/tags/v1.0` and note the hash -7. Run `git cat-file -t` on both hashes and compare the object types -8. Run `git cat-file -p` on the annotated tag hash and inspect the tagger, date, message, and target reference - -**Verify:** - -The lightweight tag hash points directly to a commit object (`git cat-file -t` prints `commit`). The annotated tag hash points to a tag object (`git cat-file -t` prints `tag`), which in turn references the commit. - -### Exercise 4: Stage, Unstage, and Inspect the Index - -**Task:** Use the index to selectively stage changes and observe its contents. - -**Steps:** - -1. In `concepts-lab`, create two files: `tracked.txt` and `experimental.txt` -2. Stage only `tracked.txt` with `git add tracked.txt` -3. Run `git ls-files --stage` to see what is in the index -4. Run `git status` and note which file is staged and which is untracked -5. Now stage `experimental.txt` with `git add experimental.txt` -6. Run `git ls-files --stage` again and confirm both entries appear -7. Remove `experimental.txt` from the index without deleting the file using `git rm --cached experimental.txt` -8. Run `git ls-files --stage` a final time and confirm only `tracked.txt` remains -9. Commit the staged file - -**Verify:** - -After step 7, `git ls-files --stage` shows only `tracked.txt`. After the commit, `git status` shows `experimental.txt` as untracked and the working tree is otherwise clean. - -### Exercise 5: Explore References and HEAD - -**Task:** Inspect how Git stores branch and tag references, and observe HEAD in attached and detached states. - -**Steps:** - -1. In `concepts-lab`, read the file `.git/HEAD` and note what it contains -2. Read `.git/refs/heads/main` and confirm it contains a commit hash -3. Run `git log -1 --format=%H` and verify the hash matches the one in the file -4. Create a new branch called `test` with `git branch test` -5. Confirm that `.git/refs/heads/test` exists and contains the same hash -6. Switch to the `test` branch with `git switch test` -7. Read `.git/HEAD` again — it should now reference `refs/heads/test` -8. Detach HEAD by running `git switch --detach HEAD~1` -9. Read `.git/HEAD` — it should now contain a raw commit hash instead of a branch reference -10. Switch back to `main` with `git switch main` - -**Verify:** - -When attached, `.git/HEAD` contains `ref: refs/heads/`. When detached, it contains a 40-character commit hash. Both `.git/refs/heads/main` and `.git/refs/heads/test` exist as files containing commit hashes. - -### Exercise 6: History Navigation with Reset - -**Task:** Practice the three reset modes and observe what each one preserves. - -**Steps:** - -1. In `concepts-lab`, create a file `reset-test.txt` with the content `version 1` and commit it -2. Change the content to `version 2`, stage, and commit -3. Change the content to `version 3`, stage, and commit -4. Run `git log --oneline` and note the three commit hashes -5. Run `git reset --soft HEAD~1` — check `git status` and read the file -6. Observe that `version 3` is still in the file and the change is staged -7. Commit it again with the message `Re-add version 3` -8. Run `git reset --mixed HEAD~1` — check `git status` and read the file -9. Observe that `version 3` is still in the file but the change is unstaged -10. Stage and commit it again with the message `Re-add version 3 again` -11. Run `git reset --hard HEAD~1` — check `git status` and read the file -12. Observe that the file now contains `version 2` — version 3 is gone -13. Use `git reflog` to find the lost commit and restore it with `git reset --hard ` - -**Verify:** - -After the final recovery, `reset-test.txt` contains `version 3` and `git log --oneline` shows the restored commit. The reflog shows all the reset operations. - -## Quiz - -**Q1.** What is the difference between a bare and a non-bare repository? - -- A) A bare repository has no branches -- B) A bare repository has no working tree — only the `.git` internals -- C) A non-bare repository cannot be pushed to -- D) A bare repository does not store commit history - -**Q2.** Which Git object type stores the contents of a file? - -- A) Commit -- B) Tree -- C) Blob -- D) Tag - -**Q3.** What is the purpose of the index (staging area)? - -- A) To store all commits in the repository -- B) To prepare and review changes before committing them -- C) To track the difference between two branches -- D) To keep a copy of the remote repository state - -**Q4.** What does HEAD point to when it is "attached"? - -- A) The root commit of the repository -- B) The most recent tag -- C) The tip of the current branch -- D) The remote tracking branch - -**Q5.** What is a hash in Git? - -- A) A password that protects a commit -- B) A unique 40-character identifier computed from an object's content -- C) The name of a branch -- D) A compressed copy of a file - -**Q6.** What happens when you run `git reset --soft HEAD~1`? - -- A) The last commit is deleted and the changes are lost -- B) HEAD and the branch tip move back one commit; the changes remain staged -- C) The file is restored from the remote repository -- D) A new commit is created that reverses the previous one - -**Q7.** In the mode `100644`, what does `100` represent? - -- A) The file size in bytes -- B) The file type (regular file) -- C) The number of links to the file -- D) The owner's user ID - -### Answers - -1. B — A bare repository has no working tree — only the `.git` internals -2. C — Blob -3. B — To prepare and review changes before committing them -4. C — The tip of the current branch -5. B — A unique 40-character identifier computed from an object's content -6. B — HEAD and the branch tip move back one commit; the changes remain staged -7. B — The file type (regular file) diff --git a/astro-site/src/content/docs/expert-topics.md b/astro-site/src/content/docs/expert-topics.md deleted file mode 100644 index 62e8917..0000000 --- a/astro-site/src/content/docs/expert-topics.md +++ /dev/null @@ -1,643 +0,0 @@ ---- -title: "Expert Topics" -section: "expert-topics" -order: 6 ---- - -## 1. Overview - -This chapter covers power-user topics — configuration layers, revision -selectors, pathspec and refspec syntax, interactive rebase, bisect, -hooks, and garbage collection. These concepts are not needed for daily -Git use but become essential as projects and teams grow. - -In this chapter you will learn: - -- How Git's layered configuration system works and how to customize it -- How to use revision selectors (tilde, caret, ranges) to navigate commit history -- How pathspec patterns filter files in Git commands -- How refspec syntax maps local and remote references -- How to rewrite commit history with interactive rebase -- How to find the commit that introduced a bug using bisect -- How to automate tasks with Git hooks -- How garbage collection and the reflog protect and clean up orphaned commits - -## 2. Configuration - -![Configuration Model](../assets/images/git-configuration-model.png) - -Git uses a layered configuration system. Settings at a more specific level -override those at a broader level: local > global > system. - -### Configuration files - -| Level | File | Scope | Flag | -|-------|------|-------|------| -| System | Git install dir (`etc/gitconfig`) | All users, all repos | `--system` | -| Global | `~/.gitconfig` | Current user, all repos | `--global` | -| Local | `.git/config` | Current repo only | `--local` | - -Edit any level with `git config -- --edit`. View all active -settings and their sources with `git config --list --show-origin`. - -### Common parameters - -| Parameter | What it does | Example | -|-----------|-------------|---------| -| `user.name` | Author name on commits | `git config --global user.name "Your Name"` | -| `user.email` | Author email on commits | `git config --global user.email "you@example.com"` | -| `init.defaultBranch` | Default branch name for new repos | `git config --global init.defaultBranch main` | -| `core.autocrlf` | Line ending conversion (set `true` on Windows, `input` on macOS/Linux) | `git config --global core.autocrlf true` | -| `core.editor` | Editor for commit messages | `git config --global core.editor "code --wait"` | -| `pull.rebase` | Use rebase instead of merge on `git pull` | `git config --global pull.rebase true` | -| `credential.helper` | Cache credentials (avoid re-entering passwords) | `git config --global credential.helper manager` | -| `merge.tool` | Default merge tool for conflict resolution | `git config --global merge.tool meld` | - -### Aliases - -Aliases create shortcuts for long commands. Define them in the global -config: - -```text -$ git config --global alias.st "status" -$ git config --global alias.co "checkout" -$ git config --global alias.hist "log --oneline --graph --all" -$ git config --global alias.unstage "restore --staged" -``` - -After setting these, `git st` runs `git status`, `git hist` shows the -full graph, etc. - -### Inspecting configuration - -```text -$ git config user.name # read a single value -$ git config --list # all active settings -$ git config --list --show-origin # all settings with source file -$ git config --list --show-scope # all settings with scope level -``` - -When the same parameter is set at multiple levels, the most specific -wins: local overrides global, global overrides system. - -## 3. Revision Selectors - -Revision selectors let you reference specific commits without knowing -their hashes. They are used with `git log`, `git diff`, `git show`, -and any command that accepts a commit reference. - -### Ancestry selectors - -#### ~ (tilde) - -The tilde moves back through **first-parent** history — a straight -line from the current commit. `HEAD~3` means "three commits back -along the first parent." - -![Tilde selector](../assets/images/git-selectors-tilde.png) - -In the diagram, `HEAD~1` is C6, `HEAD~2` is C3, `HEAD~3` is C2, -`HEAD~4` is C1. Commits C4 and C5 (second and third parents of C6) -are not reachable with `~`. - -#### ^ (caret) - -The caret selects a specific **parent** of a merge commit. `HEAD^1` -is the first parent, `HEAD^2` the second, `HEAD^3` the third. - -![Caret selector](../assets/images/git-selectors-caret.png) - -In the diagram, C6 has three parents: `HEAD~1^1` is C3, -`HEAD~1^2` is C4, `HEAD~1^3` is C5. The two operators can be -combined to navigate any commit in the graph. - -### Range selectors - -#### .. (double dot) - -Shows commits reachable from B but not from A — the difference between -two branches. - -```text -$ git log refA..refB # commits in B that A doesn't have -``` - -![Double-dot selector](../assets/images/git-selectors-double-dot.png) - -#### ... (triple dot) - -Shows commits unique to either side — the symmetric difference between -two branches. - -```text -$ git log --left-right main...feature # marks each commit < (left) or > (right) -``` - -![Triple-dot selector](../assets/images/git-selectors-triple-dot.png) - -### Reflog selectors - -Git keeps a local log called the **reflog** that records every position -HEAD and branch tips have been in (see -[Garbage Collection](#garbage-collection) for details). The `@{}` -syntax lets you reference these previous positions: - -```text -$ git show "HEAD@{1}" # previous position of HEAD -$ git show "HEAD@{yesterday}" # where HEAD was yesterday -$ git show "main@{2.weeks.ago}" # where main was 2 weeks ago -``` - -## 4. Pathspec - -A pathspec is a pattern that matches files or directories. Most Git -commands that work with files accept pathspecs. - -### Basic patterns - -```text -$ git add . # current directory -$ git add src/ # a specific directory -$ git log '*.py' # all Python files -$ git ls-files '*.mp[34]' # mp3 and mp4 files -``` - -### Wildcards - -| Pattern | Matches | -|---------|---------| -| `*` | Any number of characters | -| `?` | A single character | -| `[abc]` | One character from the set | -| `**` | Matches across directories (with `glob` signature) | - -### Magic signatures - -Signatures control the matching behavior. Syntax: `:(signature)pattern` - -| Signature | Effect | Example | -|-----------|--------|---------| -| `top` (or `/`) | Match from repo root, not current directory | `':/*.py'` | -| `exclude` (or `!`) | Remove paths from the result | `':!*.md'` | -| `icase` | Case-insensitive matching | `':(icase)*.jpg'` | -| `literal` | Treat wildcards as literal characters | `':(literal)Maybe?.mp3'` | -| `glob` | `*` stops at `/`, `**` crosses directories | `':(glob)**/*.py'` | -| `attr` | Match by `.gitattributes` values | `':(attr:!debug)*'` | - -Signatures can be combined: `':(top,icase)*.mp?'` - -## 5. Refspec - -When you run `git fetch` or `git push`, Git needs to know which -references on one side map to which references on the other. A refspec -defines this mapping. - -### Syntax - -``` -[+]: -``` - -| Part | Meaning | -|------|---------| -| `+` | Optional — force update even if not a fast-forward | -| `` | Source reference (on the remote for fetch, local for push) | -| `` | Destination reference (local for fetch, remote for push) | - -### How Git uses refspecs - -When you clone a repository, Git writes refspecs into `.git/config`: - -``` -[remote "origin"] - url = https://github.com/user/project.git - fetch = +refs/heads/*:refs/remotes/origin/* -``` - -This fetch refspec means: take every branch on the remote -(`refs/heads/*`) and store it locally as a remote-tracking branch -(`refs/remotes/origin/*`). The `+` allows non-fast-forward updates. - -### Refspec examples - -```text -$ git push origin main:refs/heads/main # push main to remote main -$ git push origin main:refs/heads/staging # push main as "staging" on remote -$ git push origin :refs/heads/feature # delete remote branch (empty src) -$ git fetch origin main:refs/remotes/origin/main # fetch one branch explicitly -``` - -## 6. Interactive Rebase - -Interactive rebase lets you edit, reorder, squash, or drop commits -before sharing them. It rewrites history — use it only on local -(unpushed) commits. - -```text -$ git rebase -i HEAD~3 # edit the last 3 commits -``` - -Git opens your editor with a list of commits and an action for each: - -``` -pick abc1234 Add login page -pick def5678 Fix typo in login -pick 789abcd Add logout button -``` - -### Actions - -| Action | Effect | -|--------|--------| -| `pick` | Keep the commit as-is | -| `reword` | Keep the commit but edit the message | -| `squash` | Merge into the previous commit, combine messages | -| `fixup` | Merge into the previous commit, discard this message | -| `edit` | Pause to amend the commit (files or message) | -| `drop` | Delete the commit entirely | -| `reorder` | Move lines up/down to change commit order | - -### Common workflows - -Squash three commits into one before a PR: - -``` -pick abc1234 Add login page -squash def5678 Fix typo in login -squash 789abcd Polish login styles -``` - -Result: one commit with a combined message replacing all three. - -> **Warning:** Interactive rebase rewrites commit hashes. Never rebase -> commits that have already been pushed to a shared branch. - -## 7. Git Bisect - -`git bisect` performs a binary search through commit history to find -the commit that introduced a bug. Instead of checking every commit, -it cuts the search space in half at each step. - -![Bisect](../assets/images/git-bisect.png) - -### Workflow - -```text -$ git bisect start -$ git bisect bad # current commit has the bug -$ git bisect good v1.0 # this older commit was working -# Git checks out a commit halfway between good and bad -# ... test it ... -$ git bisect good # this commit works → bug is in the other half -# ... Git checks out another midpoint ... -$ git bisect bad # this commit has the bug -# ... repeat until Git identifies the first bad commit ... -$ git bisect reset # return to the original branch -``` - -At each step, Git tells you how many commits remain to test. For a -range of 1000 commits, bisect finds the culprit in about 10 steps. - -### Automated bisect - -If you have a test script that exits 0 for good and non-zero for bad: - -```text -$ git bisect start HEAD v1.0 -$ git bisect run ./test.sh -``` - -Git runs the script at each midpoint automatically and reports the -first bad commit when done. - -## 8. Hooks - -Hooks are scripts that Git runs automatically before or after specific -events. They live in `.git/hooks/` and are not tracked by Git (each -clone must set up its own hooks). - -### Common hooks - -| Hook | When it runs | Typical use | -|------|-------------|-------------| -| `pre-commit` | Before a commit is created | Lint, format, run fast tests | -| `commit-msg` | After the message is entered | Enforce message format (e.g. ticket prefix) | -| `pre-push` | Before push transfers data | Run test suite, prevent push to `main` | -| `post-merge` | After a merge completes | Install dependencies, rebuild | -| `pre-rebase` | Before rebase starts | Prevent rebasing shared branches | - -### Creating a hook - -Create an executable file in `.git/hooks/` with the hook name (no -extension on macOS/Linux): - -```text -#!/bin/sh -# .git/hooks/pre-commit -# Reject commits that contain TODO -if git diff --cached --name-only | xargs grep -l 'TODO' 2>/dev/null; then - echo "Error: commit contains TODO — resolve before committing" - exit 1 -fi -``` - -```text -$ chmod +x .git/hooks/pre-commit # make executable (macOS/Linux) -``` - -### Sharing hooks - -Since `.git/hooks/` is not tracked, teams typically: - -- Store hooks in a tracked directory (e.g. `scripts/hooks/`) -- Configure Git to use it: `git config core.hooksPath scripts/hooks` -- Or use a tool like [Husky](https://typicode.github.io/husky/) (Node.js) - or [pre-commit](https://pre-commit.com/) (Python) to manage hooks - -### Bypassing hooks - -```text -$ git commit --no-verify # skip pre-commit and commit-msg hooks -$ git push --no-verify # skip pre-push hook -``` - -Use sparingly — hooks exist for a reason. - -## 9. Garbage Collection - -When you reset, rebase, or delete a branch, the commits that were on it -don't disappear immediately. They become **orphaned** — they still exist -in `.git/objects/` but no branch or tag points to them. Git's **garbage -collector** (`git gc`) is responsible for cleaning them up. - -### When does garbage collection run? - -Git runs garbage collection automatically when the number of loose -objects in `.git/objects/` exceeds a threshold (default: 6700, -configurable via `gc.auto`). You can also trigger it manually: - -```text -$ git gc # run garbage collection -$ git gc --aggressive # more thorough, slower -``` - -### How long are orphaned commits kept? - -Orphaned commits are protected by the **reflog** — a local log of -every position HEAD and branch tips have been in. As long as a commit -appears in the reflog, garbage collection will not delete it. - -By default, reflog entries expire after: - -| Type | Default expiry | -|------|---------------| -| Reachable commits (still on a branch) | 90 days | -| Unreachable commits (orphaned) | 30 days | - -This means you have **30 days** to recover an orphaned commit using -`git reflog` before it becomes eligible for deletion. - -### Changing the defaults - -```text -$ git config gc.reflogExpire 120.days.ago # reachable: 120 days -$ git config gc.reflogExpireUnreachable 60.days.ago # orphaned: 60 days -``` - -### Recovering an orphaned commit - -Use the reflog to find the commit hash, then reset or create a branch: - -```text -$ git reflog # find the hash of the lost commit -$ git branch recovered abc1234 # create a branch pointing to it -``` - -Once a branch points to the commit again, it is no longer orphaned and -will not be removed by garbage collection. - -## Exercises - -All exercises use the `concepts-lab` repository from previous chapters. - -### Exercise 1: Configuration layers - -**Task:** Set user identity at local and global levels and observe -which one takes precedence. - -**Steps:** - -1. Set a global user name and email using `git config --global` -2. Set a different local user name and email using `git config --local` -3. Run `git config --list --show-origin` to see all settings and their sources -4. Create a file, stage it, and commit -5. Run `git log` — check which identity appears in the commit -6. Remove the local overrides using `git config --local --unset user.name` - and `git config --local --unset user.email` -7. Make another commit and verify the global identity is now used - -**Verify:** - -The first commit shows the local identity. The second shows the global. -`git config --list --show-origin` marks which file each setting comes from. - -### Exercise 2: Navigate history with selectors - -**Task:** Use tilde and caret operators to explore a merge commit's -ancestry. - -**Steps:** - -1. In `concepts-lab`, ensure you have at least one merge commit in - history (from chapter 3 exercises) -2. Run `git log --oneline --graph` and identify a merge commit -3. Run `git show HEAD~1` — this is the first parent (one step back) -4. Run `git show HEAD~2` — two steps back along the first parent -5. If you have a merge commit at HEAD~1, run `git show HEAD~1^2` to - see the second parent of that merge -6. Run `git log HEAD~3..HEAD --oneline` to see the last 3 commits - -**Verify:** - -Each selector resolves to a specific commit. `HEAD~1^2` shows a -different commit than `HEAD~1^1` (or `HEAD~1`) when the commit is a -merge. - -### Exercise 3: Filter files with pathspec - -**Task:** Use pathspec patterns to filter files in Git commands. - -**Steps:** - -1. In `concepts-lab`, create files: `app.py`, `test_app.py`, - `README.md`, `docs/guide.md` -2. Stage and commit all files -3. Run `git ls-files '*.py'` — should show only Python files -4. Run `git ls-files '*.md'` — should show only Markdown files -5. Run `git log --oneline -- '*.py'` — history for Python files only -6. Run `git ls-files ':!*.md'` — everything except Markdown files - -**Verify:** - -Each command filters to the expected file set. The `-- '*.py'` syntax -works with `git log` to show only commits that touched Python files. - -### Exercise 4: Squash commits with interactive rebase - -**Task:** Combine multiple commits into one using interactive rebase. - -**Steps:** - -1. In `concepts-lab`, create three commits: - - `echo "line 1" > squash.txt && git add squash.txt && git commit -m "Add squash file"` - - `echo "line 2" >> squash.txt && git add squash.txt && git commit -m "Add line 2"` - - `echo "line 3" >> squash.txt && git add squash.txt && git commit -m "Add line 3"` -2. Run `git log --oneline -5` to see the three commits -3. Run `git rebase -i HEAD~3` -4. In the editor, change the second and third lines from `pick` to `squash` -5. Save and close — Git opens a new editor for the combined message -6. Write a single message like `Add squash file with 3 lines`, save and close -7. Run `git log --oneline -3` — the three commits are now one - -**Verify:** - -`git log --oneline` shows one commit instead of three. `cat squash.txt` -has all three lines. The commit hash is different from any of the originals. - -### Exercise 5: Find a bug with bisect - -**Task:** Use `git bisect` to find which commit introduced a change. - -**Steps:** - -1. In `concepts-lab`, create 5 commits that each add a line to `app.txt`: - - Commits 1-3: add `feature A`, `feature B`, `feature C` - - Commit 4: add `BUG` (this is the bad commit) - - Commit 5: add `feature D` -2. Run `git bisect start` -3. Mark the current commit as bad: `git bisect bad` -4. Mark the first commit as good: `git bisect good HEAD~5` -5. At each step, check if `app.txt` contains `BUG`: - - If yes: `git bisect bad` - - If no: `git bisect good` -6. Git reports the first bad commit -7. Run `git bisect reset` to return to the original branch - -**Verify:** - -Git identifies commit 4 (the one that added `BUG`) as the first bad -commit. `git bisect reset` returns you to `main`. - -### Exercise 6: Create a pre-commit hook - -**Task:** Set up a hook that prevents committing files containing TODO. - -**Steps:** - -1. In `concepts-lab`, create `.git/hooks/pre-commit` with this content: - ``` - #!/bin/sh - if git diff --cached | grep -q 'TODO'; then - echo "Error: commit contains TODO" - exit 1 - fi - ``` -2. Make it executable: `chmod +x .git/hooks/pre-commit` -3. Create a file `task.txt` with the content `TODO: finish this` -4. Stage and try to commit — the hook should reject it -5. Edit `task.txt` to remove `TODO`, stage again, and commit -6. The commit should succeed - -**Verify:** - -The first commit attempt fails with "Error: commit contains TODO". -After removing TODO, the commit succeeds. - -### Exercise 7: Recover an orphaned commit - -**Task:** Delete a branch, then recover it using the reflog. - -**Steps:** - -1. Create and switch to `feature/recover`, make a commit -2. Switch back to `main` -3. Delete the branch: `git branch -D feature/recover` -4. Run `git reflog` and find the hash of the deleted branch's commit -5. Create a new branch at that hash: `git branch recovered ` -6. Switch to `recovered` and verify the commit is intact - -**Verify:** - -`git log --oneline` on `recovered` shows the commit that was on the -deleted branch. The reflog entry matches the hash. - -## Quiz - -**Q1.** What does `HEAD~3` refer to? - -- A) The third parent of a merge commit -- B) The commit three steps back following the first parent -- C) The third branch in the repository -- D) The third file in the commit - -**Q2.** What does `HEAD^2` refer to? - -- A) Two commits back along the first parent -- B) The second parent of the current commit -- C) The second file in the commit -- D) The previous branch - -**Q3.** What does the pathspec `':(icase)*.JPG'` match? - -- A) Only files named exactly `*.JPG` -- B) Files ending in `.jpg`, `.JPG`, `.Jpg`, or any case variation -- C) Only uppercase filenames -- D) Files in the `JPG` directory - -**Q4.** In a refspec `+refs/heads/*:refs/remotes/origin/*`, what does -the `+` mean? - -- A) Add a new remote -- B) Force update even if not a fast-forward -- C) Only fetch new branches -- D) Push and pull at the same time - -**Q5.** What does the `squash` action do in interactive rebase? - -- A) Deletes the commit -- B) Merges the commit into the previous one, combining messages -- C) Moves the commit to a different branch -- D) Reverts the commit's changes - -**Q6.** How does `git bisect` find the commit that introduced a bug? - -- A) It checks every commit from newest to oldest -- B) It performs a binary search, halving the range at each step -- C) It runs `git blame` on every file -- D) It compares the first and last commits only - -**Q7.** Where do Git hooks live? - -- A) In the repository root -- B) In `.git/hooks/` -- C) In `~/.githooks/` -- D) In the staging area - -**Q8.** How long does Git keep orphaned commits before garbage -collection removes them? - -- A) They are deleted immediately -- B) 7 days -- C) 30 days (default reflog expiry for unreachable commits) -- D) Forever — they are never deleted - -### Answers - -1. B — The commit three steps back following the first parent -2. B — The second parent of the current commit -3. B — Files ending in `.jpg`, `.JPG`, `.Jpg`, or any case variation -4. B — Force update even if not a fast-forward -5. B — Merges the commit into the previous one, combining messages -6. B — It performs a binary search, halving the range at each step -7. B — In `.git/hooks/` -8. C — 30 days (default reflog expiry for unreachable commits) diff --git a/astro-site/src/content/docs/glossary.md b/astro-site/src/content/docs/glossary.md deleted file mode 100644 index beea089..0000000 --- a/astro-site/src/content/docs/glossary.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: "Glossary" -section: "glossary" -order: 9 ---- - -## Glossary - -A reference of key Git terms used throughout this tutorial, with links to the chapter where each concept is covered. - -| Term | Definition | Chapter | -|------|-----------|---------| -| Annotated tag | A tag object with author, date, and message — stored in `.git/objects/` | [2](../building-blocks/) | -| Bare repository | A repository with no working tree — only `.git/` internals | [2](../building-blocks/) | -| Bisect | Binary search through commit history to find the commit that introduced a bug | [6](../expert-topics/) | -| Blame | Show which commit and author last modified each line of a file | [7](../playbook/) | -| Blob | Object that stores the raw contents of a single file | [2](../building-blocks/) | -| Branch | A movable pointer to a commit, stored in `.git/refs/heads/` | [2](../building-blocks/) | -| Cherry-pick | Copy a single commit from one branch onto another, creating a new commit with a different hash | [3](../branching-and-merging/) | -| Clone | Create a local copy of a remote repository, including full history, origin remote, and tracking branches | [4](../remote-repositories/) | -| Commit | An object that records a snapshot of the project — references a tree, parent commits, author, and message | [2](../building-blocks/) | -| Conflict | When two branches modify the same lines and Git cannot merge them automatically | [3](../branching-and-merging/) | -| Detached HEAD | State where HEAD points directly to a commit instead of a branch — new commits are orphaned if you switch away | [2](../building-blocks/) | -| Fast-forward | A merge where the target branch simply moves forward to the source branch tip — no merge commit is created | [3](../branching-and-merging/) | -| Fetch | Download commits from a remote and update remote-tracking branches without modifying local branches | [4](../remote-repositories/) | -| Fork | A hosting-platform copy of someone else's repository under your account | [4](../remote-repositories/) | -| Garbage collection | Git's process for removing orphaned objects from `.git/objects/` | [6](../expert-topics/) | -| Hash | A unique 40-character identifier (SHA-1) computed from an object's content | [2](../building-blocks/) | -| HEAD | Reference to the current position — usually points to a branch, sometimes directly to a commit (detached) | [2](../building-blocks/) | -| Hook | A script in `.git/hooks/` that Git runs automatically before or after events like commit or push | [6](../expert-topics/) | -| Index | The staging area — a sorted list of tracked files prepared for the next commit, stored at `.git/index` | [2](../building-blocks/) | -| Interactive rebase | Editing, reordering, squashing, or dropping commits before sharing them | [6](../expert-topics/) | -| Lightweight tag | A tag that is just a file in `.git/refs/tags/` containing a commit hash — no object, no metadata | [2](../building-blocks/) | -| Merge | Combining changes from two branches into one, optionally creating a merge commit | [3](../branching-and-merging/) | -| Merge commit | A commit with two or more parents, created by a 3-way merge | [3](../branching-and-merging/) | -| Origin | Conventional name for the remote you cloned from | [4](../remote-repositories/) | -| Orphaned commit | A commit no branch or tag points to — eligible for garbage collection after reflog expiry | [6](../expert-topics/) | -| Pathspec | A pattern that matches files or directories in Git commands | [6](../expert-topics/) | -| Pull | Fetch from a remote and merge (or rebase) into the current branch | [4](../remote-repositories/) | -| Pull request | A hosting-platform feature for requesting review and merge of a branch | [4](../remote-repositories/) | -| Push | Upload local commits to a remote branch | [4](../remote-repositories/) | -| Rebase | Replay commits from one branch on top of another, producing a linear history | [3](../branching-and-merging/) | -| Reflog | A local log of every position HEAD and branch tips have been in — used to recover lost commits | [6](../expert-topics/) | -| Refspec | Syntax that maps references between a remote and a local repository (e.g. `+refs/heads/*:refs/remotes/origin/*`) | [6](../expert-topics/) | -| Remote | A named reference to another repository, stored in `.git/config` | [4](../remote-repositories/) | -| Remote-tracking branch | A read-only local reference that mirrors a remote branch (e.g. `origin/main`), updated by fetch and pull | [4](../remote-repositories/) | -| Repository | The `.git/` directory containing all objects, references, and configuration for a project | [2](../building-blocks/) | -| Reset | Move HEAD and optionally the branch tip to a different commit — `--soft`, `--mixed`, or `--hard` | [2](../building-blocks/) | -| Revert | Create a new commit that undoes a previous commit's changes without rewriting history | [7](../playbook/) | -| Squash merge | Combine all commits from a branch into a single change set on the target branch — no merge commit | [3](../branching-and-merging/) | -| Stash | Save uncommitted changes temporarily so you can switch branches with a clean working tree | [3](../branching-and-merging/) | -| Submodule | A reference to a specific commit in another repository — stores URL and hash, not files | [5](../subprojects/) | -| Subtree | A full copy of another repository merged into a subdirectory of the parent project | [5](../subprojects/) | -| Tag | A named reference to a commit — annotated (object with metadata) or lightweight (plain reference) | [2](../building-blocks/) | -| Tree | Object that represents a directory — lists blobs and other trees with names and permissions | [2](../building-blocks/) | -| Upstream | Conventional name for the original repository you forked from | [4](../remote-repositories/) | -| Working tree | The checked-out files on disk that you edit directly — everything outside `.git/` | [2](../building-blocks/) | diff --git a/astro-site/src/content/docs/introduction.md b/astro-site/src/content/docs/introduction.md deleted file mode 100644 index 1acbdc0..0000000 --- a/astro-site/src/content/docs/introduction.md +++ /dev/null @@ -1,337 +0,0 @@ ---- -title: "Introduction" -section: "introduction" -order: 1 ---- - -## 1. Overview - -Git is a version control system. It keeps a complete history of every change -you make to your files, so you can go back to any earlier version at any time. -Think of it as an unlimited undo button for your entire project. - -Why use version control? - -- Your work is protected against accidental deletion or hardware failure -- Every change is recorded — you can see who changed what, and when -- You can review changes before saving them permanently -- Multiple people can work on the same project without overwriting each other -- You can experiment on a separate branch and merge it back when it works - -In this chapter you will learn: - -- What Git is and why version control matters -- How to install Git on your platform -- Where to host your repositories -- How Git moves changes through workspace, index, and repository -- The most common commands in daily operations - -## 2. Features - -Git is **open source** and free to use. Unlike older systems such as -Subversion (SVN) where all history lives on a central server, Git is -**distributed** — everyone working on a project has a full copy on their -own machine. This means you can work offline, save changes locally, and -share with others when you are ready. - -- **Open source** — free, community-maintained, runs on every major platform -- **Distributed** — every copy contains the complete project and its full history -- **Branching** — work on separate ideas at the same time without interfering with each other -- **Fast** — most operations happen on your own machine, with no waiting for a server - -## 3. Installation - -### Download - -Go to https://git-scm.com/downloads and download the installer for -your operating system. - -### Install - -| OS | How to install | -|----|---------------| -| Windows | Run the downloaded installer with the default options | -| macOS | Run `xcode-select --install` in Terminal, or `brew install git` if you use Homebrew | -| Ubuntu / Debian | `sudo apt-get install git` | -| Fedora | `sudo dnf install git` | -| Arch Linux | `sudo pacman -S git` | - -### Verify - -Open a terminal and run: - -```text -$ git --version -git version 2.47.1 -``` - -If you see a version number, Git is installed. - -## 4. Hosting - -A Git hosting service stores your repositories online so you can access them -from anywhere and collaborate with others. You do not need your own server — -the hosting provider handles storage, backups and access control. - -### Main providers - -- [GitHub](https://github.com/) — largest community, default for open source -- [GitLab](https://about.gitlab.com/) — built-in automation for testing and deployment, can be self-hosted -- [Bitbucket](https://bitbucket.org/) — integrates with Jira and other Atlassian tools - -All three offer free plans for individuals and small teams. - -### Competitive Matrix - -The table below compares the free tiers of each provider. - -![Git Hosting Comparison](../assets/images/git-hosting.png) - -## 5. How Git Works - -Git moves your changes through three locations before they are shared -with others. The diagram below shows these locations and the commands -that transfer data between them. - -![How Git Works](../assets/images/git-dataflow-diagram.png) - -### Workspace - -The workspace (also called *worktree*) is the project folder on your -computer. This is where you create, edit and delete files. Changes here -are not yet tracked by Git — they exist only on your hard drive. - -### Index - -The index (also called the *staging area*) is a holding area where you -prepare the next commit. You pick which changes to include by adding -them to the index with `git add`. This lets you commit related changes -together, even if you modified many files. - -### Repository - -The repository stores the full history of your project as a series of -snapshots called *commits*. Each commit records exactly what the project -looked like at that moment. The repository can be **local** (on your -machine) or **remote** (on a hosting service like GitHub). Git treats -both as equals — there is no single authoritative copy. (The word "master" -here means primary, not the branch name `master` — Git uses `main` as the -default branch name.) - -### How a Commit Works - -When you run `git commit`, Git takes a snapshot of everything in the index -and stores it permanently in the repository. Each snapshot is called a -*commit* and gets a unique identifier called a *hash* — a long string of -letters and numbers that acts like a fingerprint for that snapshot. Every -commit also records who made it, when, and a short message describing -what changed. - -Each commit points back to the one before it, forming a chain. This chain -is the history of your project — you can follow it backwards to see exactly -how the project evolved, one commit at a time. - -``` -A ← B ← C ← D (main) - ↑ - each commit points to its parent -``` - -In the diagram above, `D` is the latest commit. It points back to `C`, -which points to `B`, and so on. Git follows these links to reconstruct the -full history. - -## 6. Command Overview - -![Command Overview](../assets/images/git-command-overview.png) - -Commands are grouped by category and experience level: - -| Level | Categories | -|-------|-----------| -| Beginner | Help, Create, Configure, Track, Inspect | -| Advanced | Sync, Revert, Branch, Reuse | -| Expert | Rewrite, Cleanup (can destroy history) | - -Each category is covered in detail in the following chapters. The -table below lists the most common commands in daily operations. - -| Command | What it does | Example | -|---------|-------------|---------| -| `git clone` | Copy a remote repository | `git clone https://github.com/user/repo.git` | -| `git status` | Show working tree and index state | `git status` | -| `git add` | Stage changes for the next commit | `git add README.md` | -| `git commit` | Save a snapshot of staged changes | `git commit -m "Fix typo"` | -| `git push` | Upload commits to a remote | `git push origin main` | -| `git pull` | Download and integrate remote changes | `git pull origin main` | -| `git log` | Show commit history | `git log --oneline` | -| `git diff` | Show changes between commits or working tree | `git diff --staged` | -| `git branch` | List or create branches | `git branch feature` | -| `git switch` | Change the current branch | `git switch feature` | -| `git merge` | Integrate another branch into the current one | `git merge feature` | -| `git restore` | Discard or unstage changes | `git restore README.md` | - -Two things worth noting: - -- Use `git switch` (added in Git 2.23) to change branches. Older - tutorials use `git checkout` — both work, but `switch` is the - recommended command. -- Create a `.gitignore` file to tell Git which files to skip — build - output, editor settings, credentials and other files that should not - be tracked. - -## Exercises - -These exercises walk you through a complete Git workflow from start to -finish. Each step builds on the previous one. - -### Exercise 1: Install Git - -**Task:** Install Git and verify it works. - -**Steps:** - -1. Follow the installation instructions for your operating system above -2. Open a terminal and run `git --version` -3. Set your identity so Git can label your commits: - - `git config --global user.name "Your Name"` - - `git config --global user.email "you@example.com"` - -**Verify:** `git --version` prints a version number. - ---- - -### Exercise 2: Clone a Remote Repository - -**Task:** Get a copy of an existing repository from GitHub. - -**Steps:** - -1. Sign in to GitHub and create a new repository named `git-exercises` - — check "Add a README file" -2. Copy the HTTPS URL of the repository -3. In your terminal, run `git clone ` -4. Enter the `git-exercises` directory - -**Verify:** The directory contains a `README.md` file. -`git log` shows one commit. - ---- - -### Exercise 3: Add and Commit a Change - -**Task:** Create a file, stage it, and commit it. - -**Steps:** - -1. Create a file called `hello.txt` with some text in it -2. Run `git status` — the file appears as untracked -3. Run `git add hello.txt` to stage it -4. Run `git status` — the file appears as staged -5. Run `git commit -m "Add hello.txt"` - -**Verify:** `git log` shows two commits. `git status` reports a clean -working tree. - ---- - -### Exercise 4: Push to the Remote - -**Task:** Upload your local commit to GitHub. - -**Steps:** - -1. Run `git push` -2. Refresh the repository page on GitHub - -**Verify:** `hello.txt` appears in the repository on GitHub. - ---- - -### Exercise 5: Pull from the Remote - -**Task:** Edit a file on GitHub and pull the change to your local machine. - -**Steps:** - -1. On GitHub, click `README.md` and edit it — add a line of text -2. Commit the change directly on GitHub -3. In your terminal, run `git pull` -4. Open `README.md` locally - -**Verify:** The local file contains the line you added on GitHub. -`git log` shows three commits. - ---- - -### Exercise 6: Track a Change - -**Task:** Modify a file and walk it through the full pipeline — -workspace, index, repository, remote. - -**Steps:** - -1. Edit `hello.txt` and add a second line -2. Run `git diff` to see the change -3. Run `git add hello.txt` -4. Run `git diff --staged` to see what will be committed -5. Run `git commit -m "Update hello.txt"` -6. Run `git push` - -**Verify:** `git log` shows four commits. GitHub shows the updated file. - -## Quiz - -Test your understanding of the concepts covered in this chapter. - -**Q1.** What are the three locations that Git moves changes through before -they are shared with others? - -- A) Workspace, Index, Repository -- B) Editor, Terminal, Server -- C) Branch, Commit, Push -- D) Local, Cloud, Backup - -**Q2.** What does `git commit` actually save? - -- A) Every file in the workspace -- B) A snapshot of everything in the index -- C) Only the files that changed since the last commit -- D) A copy of the remote repository - -**Q3.** What makes Git different from centralised systems like SVN? - -- A) Git requires an internet connection at all times -- B) Git only stores the latest version of each file -- C) Every copy contains the complete project and its full history -- D) Git does not support branching - -**Q4.** What is a commit hash? - -- A) A password that protects the commit -- B) A short description of the change -- C) A unique identifier that acts like a fingerprint for a snapshot -- D) The name of the branch the commit belongs to - -**Q5.** What is the purpose of the index (staging area)? - -- A) To store the remote repository URL -- B) To prepare which changes go into the next commit -- C) To keep a backup of deleted files -- D) To track which branches exist - -**Q6.** How are commits connected to each other? - -- A) They share the same file names -- B) Each commit points back to its parent, forming a chain -- C) They are sorted alphabetically by message -- D) The branch name links them together - -### Answers - -1. A — Workspace, Index, Repository -2. B — A snapshot of everything in the index -3. C — Every copy contains the complete project and its full history -4. C — A unique identifier that acts like a fingerprint for a snapshot -5. B — To prepare which changes go into the next commit -6. B — Each commit points back to its parent, forming a chain diff --git a/astro-site/src/content/docs/playbook.md b/astro-site/src/content/docs/playbook.md deleted file mode 100644 index 22496e5..0000000 --- a/astro-site/src/content/docs/playbook.md +++ /dev/null @@ -1,419 +0,0 @@ ---- -title: "Playbook" -section: "playbook" -order: 7 ---- - -## 1. Overview - -This chapter is a quick-reference collection of recipes for common -Git tasks. Each recipe shows the problem, the commands to solve it, -and what to watch out for. - -For command syntax, see [Appendix](../appendix/). For definitions, -see [Glossary](../glossary/). - -In this chapter you will learn: - -- How to undo changes at every level — unstage, reset, revert, and recover -- Branching recipes — create, delete, rename, and inspect branches -- Merging recipes — fast-forward, no-ff, squash, conflict resolution -- Rebasing — linearize history and squash commits interactively -- Remote operations — push, pull, force push safely, sync forks -- Cherry-picking — apply individual commits across branches -- Stashing — save and restore work in progress -- Tagging — create, push, and delete annotated tags -- Submodules — add, clone, update, and remove submodules -- Debugging — bisect, blame, and search commit history -- Configuration — identity, defaults, aliases, and diagnostics - -## 2. Undoing changes - -### Discard unstaged changes to a file - -```text -$ git restore -``` - -Reverts the file in your working tree to match the last commit. -Uncommitted edits are lost. - -### Unstage a file without losing changes - -```text -$ git restore --staged -``` - -Removes the file from the index but keeps your edits in the working -tree. - -### Undo the last commit but keep changes staged - -```text -$ git reset --soft HEAD~1 -``` - -The commit is removed but your changes stay in the index, ready to -recommit. - -### Undo the last commit and unstage changes - -```text -$ git reset HEAD~1 -``` - -The default (`--mixed`). Changes go back to the working tree as -unstaged edits. - -### Undo the last commit and discard all changes - -```text -$ git reset --hard HEAD~1 -``` - -The commit and all changes are gone. Recover with `git reflog` if -needed. - -### Revert a commit without rewriting history - -```text -$ git revert -``` - -Creates a new commit that undoes the changes. Safe to use on shared -branches. - -### Recover a lost commit - -```text -$ git reflog # find the hash -$ git branch recovered # or: git reset --hard -``` - -Works as long as the reflog entry has not expired (default: 30 days). - -## 3. Branching - -### Create a branch and switch to it - -```text -$ git switch -c feature/name -``` - -### Delete a branch (local and remote) - -```text -$ git branch -d feature/name # local (safe — only if merged) -$ git branch -D feature/name # local (force — even if unmerged) -$ git push origin --delete feature/name # remote -``` - -### Rename the current branch - -```text -$ git branch -m new-name -``` - -### See which branches are merged into main - -```text -$ git branch --merged main -``` - -Safe to delete any branch listed here (except `main` itself). - -## 4. Merging - -### Merge a feature branch into main - -```text -$ git switch main -$ git merge feature/name -``` - -### Force a merge commit (no fast-forward) - -```text -$ git merge --no-ff feature/name -``` - -### Squash a branch into a single commit - -```text -$ git merge --squash feature/name -$ git commit -m "Add feature" -``` - -### Abort a conflicted merge - -```text -$ git merge --abort -``` - -Returns everything to the pre-merge state. - -### Resolve a merge conflict - -```text -$ git status # identify conflicting files -# ... edit each file, remove markers ... -$ git add # stage resolved file -$ git commit # complete the merge -``` - -## 5. Rebasing - -### Rebase a feature branch onto main - -```text -$ git switch feature/name -$ git rebase main -``` - -### Squash commits with interactive rebase - -```text -$ git rebase -i HEAD~3 -# change "pick" to "squash" for commits to combine -``` - -### Abort a conflicted rebase - -```text -$ git rebase --abort -``` - -### Continue after resolving a rebase conflict - -```text -$ git add -$ git rebase --continue -``` - -## 6. Remote operations - -### Push a new branch and set up tracking - -```text -$ git push -u origin feature/name -``` - -### Pull with rebase (avoid merge commits) - -```text -$ git pull --rebase origin main -``` - -### Fix a rejected push - -```text -$ git pull origin main # fetch + merge -$ git push origin main # retry -``` - -### Force push safely (after rebase) - -```text -$ git push --force-with-lease origin feature/name -``` - -Never use `--force` on shared branches. - -### Sync a fork with upstream - -```text -$ git fetch upstream -$ git switch main -$ git merge upstream/main -$ git push origin main -``` - -### Fetch and inspect before merging - -```text -$ git fetch origin -$ git log main..origin/main --oneline # what's new on remote -$ git diff main origin/main # line-by-line changes -$ git merge origin/main # integrate when ready -``` - -## 7. Cherry-picking - -### Apply a single commit from another branch - -```text -$ git cherry-pick -``` - -### Cherry-pick without committing (stage only) - -```text -$ git cherry-pick --no-commit -``` - -Useful when you want to modify the change before committing. - -## 8. Stashing - -### Save work in progress - -```text -$ git stash push -m "description" -``` - -### Save including untracked files - -```text -$ git stash push -u -m "description" -``` - -### Restore the latest stash - -```text -$ git stash pop # restore and remove from stash -$ git stash apply # restore but keep in stash -``` - -### List and drop stash entries - -```text -$ git stash list # show all entries -$ git stash drop stash@{0} # delete a specific entry -$ git stash clear # delete all entries -``` - -## 9. Tagging - -### Create an annotated tag - -```text -$ git tag -a v1.0.0 -m "First release" -``` - -### Tag a past commit - -```text -$ git tag -a v0.9.0 -m "Beta release" -``` - -### Push tags to remote - -```text -$ git push origin v1.0.0 # single tag -$ git push origin --tags # all tags -``` - -### Delete a tag (local and remote) - -```text -$ git tag -d v1.0.0 # local -$ git push origin --delete v1.0.0 # remote -``` - -## 10. Submodules - -### Add a submodule - -```text -$ git submodule add -$ git commit -m "Add submodule" -``` - -### Clone a repo with submodules - -```text -$ git clone --recurse-submodules -``` - -Or after a regular clone: - -```text -$ git submodule update --init --recursive -``` - -### Update a submodule to latest - -```text -$ git submodule update --remote -$ git add -$ git commit -m "Update submodule" -``` - -### Remove a submodule - -```text -$ git submodule deinit -$ git rm -$ rm -rf .git/modules/ -$ git commit -m "Remove submodule" -``` - -## 11. Debugging - -### Find which commit introduced a bug - -```text -$ git bisect start -$ git bisect bad # current commit has the bug -$ git bisect good # this older commit was fine -# ... test each midpoint, mark good/bad ... -$ git bisect reset # done — return to original branch -``` - -### Automated bisect with a test script - -```text -$ git bisect start HEAD -$ git bisect run ./test.sh -``` - -### See who last changed each line - -```text -$ git blame -``` - -### Search commit messages - -```text -$ git log --grep="fix" --oneline -``` - -### Search file contents across history - -```text -$ git log -S "functionName" --oneline -``` - -## 12. Configuration - -### Set identity - -```text -$ git config --global user.name "Your Name" -$ git config --global user.email "you@example.com" -``` - -### Set default branch name - -```text -$ git config --global init.defaultBranch main -``` - -### Set default pull strategy to rebase - -```text -$ git config --global pull.rebase true -``` - -### Create an alias - -```text -$ git config --global alias.hist "log --oneline --graph --all" -``` - -### See where a setting comes from - -```text -$ git config --list --show-origin -``` diff --git a/astro-site/src/content/docs/remote-repositories.md b/astro-site/src/content/docs/remote-repositories.md deleted file mode 100644 index cf3965a..0000000 --- a/astro-site/src/content/docs/remote-repositories.md +++ /dev/null @@ -1,468 +0,0 @@ ---- -title: "Remote Repositories" -section: "remote-repositories" -order: 4 ---- - -## 1. Overview - -This chapter covers how Git communicates with remote repositories — -fetching changes others have made, pushing your own work, and keeping -local and remote branches in sync. These are the operations that turn -Git from a local history tool into a collaboration platform. - -The basics of connecting to a remote were introduced in -[Introduction](../introduction/) (Exercise 6). This chapter goes -deeper into the mechanics and the workflows you will use daily. - -In this chapter you will learn: - -- How remote branches and remote-tracking references work -- How to clone a repository and what Git sets up automatically -- How to fetch changes from a remote without modifying your working tree -- How to pull remote changes and handle conflicts -- How to push local commits to a remote branch -- How forking workflows enable contribution to external projects - -![Remote flow](../assets/images/git-remote-flow.png) - -## 2. Remote Branches - -A remote is a named reference to another repository, usually hosted on -a service like GitHub, GitLab, or Bitbucket. When you clone a -repository, Git automatically creates a remote called `origin` that -points to the URL you cloned from. - -### Listing remotes - -```text -$ git remote # list remote names -$ git remote -v # list names with URLs -``` - -Example output: - -``` -origin https://github.com/user/project.git (fetch) -origin https://github.com/user/project.git (push) -``` - -The fetch and push URLs are usually the same, but they can differ. - -### Adding a remote - -```text -$ git remote add upstream https://github.com/original/project.git -``` - -This registers a new remote called `upstream`. You can choose any name, -but `origin` and `upstream` are conventional: - -| Name | Convention | -|------------|-----------------------------------------------| -| `origin` | Your own copy (the one you cloned or created) | -| `upstream` | The original repository you forked from | - -### Renaming and removing remotes - -```text -$ git remote rename old-name new-name -$ git remote remove upstream -``` - -Removing a remote also deletes all its remote-tracking branches. - -### Checking sync status - -For every branch on a remote, Git keeps a local read-only reference -called a remote-tracking branch. These follow the pattern -`/`: - -``` -origin/main -origin/feature -upstream/main -``` - -As covered in [Building Blocks](../building-blocks/), these references -live in `.git/refs/remotes/`. They are updated automatically by `fetch` -and `pull`, never by your local commits. - -```text -$ git branch -vv -``` - -This shows each local branch, its tracking relationship, and whether -it is ahead, behind, or diverged: - -``` -* main abc1234 [origin/main] Latest commit message - feature def5678 [origin/feature: ahead 2] Work in progress -``` - -| Status | Meaning | -|-------------------|-----------------------------------------------| -| ahead 2 | You have 2 local commits not yet pushed | -| behind 3 | The remote has 3 commits you have not fetched | -| ahead 1, behind 2 | Both sides have new commits (diverged) | - -## 3. Cloning - -Cloning creates a local copy of a remote repository. It downloads the -full history, sets up `origin` pointing to the source URL, creates -remote-tracking branches for every remote branch, and checks out the -default branch. - -![Clone](../assets/images/git-remote-clone.png) - -```text -$ git clone https://github.com/user/project.git -$ git clone https://github.com/user/project.git my-folder # custom directory name -``` - -Git supports two URL protocols: - -| Protocol | URL format | Notes | -|----------|------------------------------------|---------------------------------------------| -| HTTPS | `https://github.com/user/repo.git` | Works everywhere, prompts for credentials | -| SSH | `git@github.com:user/repo.git` | Requires SSH key setup, no password prompts | - -HTTPS is simpler to start with. SSH is covered in the -[Appendix](../appendix/). - -## 4. Fetching - -Fetching downloads commits, branches, and tags from a remote and -updates the remote-tracking branches. It does not modify your working -tree or local branches. - -```text -$ git fetch origin # fetch all branches from origin -$ git fetch origin main # fetch only the main branch -$ git fetch --all # fetch from all configured remotes -``` - -After fetching, you can inspect what changed before deciding to -integrate: - -```text -$ git log main..origin/main # commits on remote that you don't have -$ git diff main origin/main # line-by-line differences -``` - -Fetching is always safe — it never changes your local branches or -working tree. - -## 5. Pulling - -Pulling combines two operations in one command: fetch followed by merge. - -```text -$ git pull origin main # equivalent to fetch + merge -``` - -### Pull with rebase - -By default, `git pull` creates a merge commit when your branch has -diverged from the remote. To produce a linear history instead, use -rebase: - -```text -$ git pull --rebase origin main -``` - -This replays your local commits on top of the remote changes, avoiding -the merge commit. Many teams prefer this for feature branches to keep -the history clean. - -To make rebase the default pull strategy: - -```text -$ git config --global pull.rebase true -``` - -### Handling pull conflicts - -If the remote changes conflict with your local changes, Git stops and -asks you to resolve the conflict — the same process described in -[Branching and Merging](../branching-and-merging/#conflicts). After -resolving: - -```text -$ git add -$ git commit # if pulling with merge -$ git rebase --continue # if pulling with rebase -``` - -## 6. Pushing - -Pushing uploads your local commits to a remote branch. The remote -branch is updated to match your local branch. - -```text -$ git push origin main -``` - -### Setting upstream tracking - -The `-u` flag links your local branch to a remote branch so that -future `push` and `pull` commands work without specifying the remote -and branch name: - -```text -$ git push -u origin feature # first push — sets up tracking -$ git push # subsequent pushes — no arguments needed -``` - -### Rejected pushes - -A push is rejected when the remote branch has commits that your local -branch does not have: - -``` -! [rejected] main -> main (non-fast-forward) -``` - -This means someone else pushed changes since your last fetch. To fix -this: - -1. Pull the remote changes: `git pull origin main` -2. Resolve any conflicts -3. Push again: `git push origin main` - -### Force pushing - -Force pushing overwrites the remote branch with your local history: - -| Command | Behavior | -|-------------------------------|-------------------------------------------------------| -| `git push --force` | Overwrites unconditionally — can discard others' work | -| `git push --force-with-lease` | Fails if someone else pushed since your last fetch | - -Always prefer `--force-with-lease` over `--force`. - -> **Warning:** Never force push to shared branches like `main`. It -> rewrites history for everyone and can cause data loss. Use force push -> only on your own feature branches. - -## 7. Forking - -Forking is a hosting-platform feature (not a Git command) that creates -your own copy of someone else's repository under your account. This is -the standard way to contribute to projects you do not have write access -to. - -![Forking workflow](../assets/images/git-remote-fork.png) - -### Setup - -1. **Fork** the repository on the hosting platform (e.g. GitHub) -2. **Clone** your fork locally: - ```shell - $ git clone https://github.com/you/project.git - ``` -3. **Add the original as upstream**: - ```shell - $ git remote add upstream https://github.com/original/project.git - ``` - -### Contributing - -1. Create a feature branch from an up-to-date `main`: - ```shell - $ git fetch upstream - $ git switch -c feature/my-change upstream/main - ``` -2. Make your changes and commit -3. Push to your fork: - ```shell - $ git push -u origin feature/my-change - ``` -4. Open a **pull request** from your fork's branch to the original - repository's `main` branch - -### Keeping your fork in sync - -```text -$ git fetch upstream -$ git switch main -$ git merge upstream/main -$ git push origin main -``` - -This pulls the latest changes from the original repository into your -fork. Do this regularly to avoid large divergences. - -## Exercises - -All exercises use the `concepts-lab` repository from previous chapters. - -### Exercise 1: Clone and inspect a repository - -**Task:** Clone a repository and explore what Git sets up automatically. - -**Steps:** - -1. On GitHub, create a new repository called `clone-lab` with a README -2. Clone it locally: `git clone clone-lab` -3. Enter the directory and run `git remote -v` -4. Run `git branch -vv` to see the tracking relationship -5. Run `git log --oneline` to confirm the initial commit is present -6. List the remote-tracking branches: `git branch -r` -7. Inspect `.git/refs/remotes/origin/` to see the tracking reference - -**Verify:** - -`git remote -v` shows `origin` pointing to your GitHub URL. -`git branch -vv` shows `main` tracking `origin/main`. -`git branch -r` lists `origin/main`. - -### Exercise 2: Fetch and inspect before merging - -**Task:** Practice the fetch-then-inspect workflow instead of pulling -directly. - -**Steps:** - -1. On GitHub, edit a file directly in the browser on the `main` branch - (add a comment line to any file) and commit the change -2. Back in your terminal, run `git fetch origin` -3. Run `git log main..origin/main --oneline` to see what changed -4. Run `git diff main origin/main` to see the exact differences -5. Once satisfied, run `git merge origin/main` to integrate the changes -6. Confirm with `git log --oneline` that the remote commit is now in - your local history - -**Verify:** - -After merging, `git status` shows your branch is up to date with -`origin/main`. The commit made on GitHub appears in `git log`. - -### Exercise 3: Handle a rejected push - -**Task:** Simulate a rejected push and resolve it. - -**Steps:** - -1. On GitHub, edit a file on `main` and commit (simulating a teammate's - push) -2. Locally, edit a different file on `main` and commit -3. Run `git push origin main` — it should be rejected with - `non-fast-forward` -4. Run `git pull origin main` to fetch and merge the remote changes -5. If there are no conflicts, Git creates a merge commit automatically -6. Run `git push origin main` — it should succeed -7. Run `git log --oneline --graph` to see the merge in history - -**Verify:** - -`git log --graph` shows the divergence and merge. `git status` reports -the branch is up to date with `origin/main`. - -### Exercise 4: Push with upstream tracking - -**Task:** Set up upstream tracking and verify it simplifies push/pull. - -**Steps:** - -1. In `concepts-lab`, create and switch to a new branch `feature/tracking` -2. Create a file `tracking.txt`, stage and commit -3. Push with the `-u` flag: `git push -u origin feature/tracking` -4. Run `git branch -vv` to confirm the tracking relationship -5. Make another change, commit, and run `git push` with no arguments -6. Confirm the push succeeded without specifying remote or branch - -**Verify:** - -`git branch -vv` shows `feature/tracking` tracking `origin/feature/tracking`. -The second push works with no arguments. - -### Exercise 5: Fork and contribute - -**Task:** Practice the forking workflow using your own `concepts-lab` -repository as the "original" project. - -**Steps:** - -1. On GitHub, open `concepts-lab` and click "Fork" to create a fork - under your own account (GitHub allows forking your own repos into - an organization, or you can use a second account) -2. Clone the fork locally into a new directory: - `git clone concepts-lab-fork` -3. Enter the directory and add the original as upstream: - `git remote add upstream ` -4. Verify with `git remote -v` — you should see both `origin` (fork) - and `upstream` (original) -5. Create a feature branch: `git switch -c feature/fork-test` -6. Create a file `fork-test.txt`, commit it, and push to your fork: - `git push -u origin feature/fork-test` -7. On GitHub, open a pull request from the fork's branch to the - original repository - -**Verify:** - -`git remote -v` shows two remotes. The pull request appears on the -original repository's GitHub page. - -## Quiz - -**Q1.** What does `git clone` set up automatically? - -- A) Only the working tree — no remote or tracking branches -- B) A local copy, an `origin` remote, and remote-tracking branches -- C) A bare repository with no working tree -- D) A fork on the hosting platform - -**Q2.** What does `git fetch` do? - -- A) Downloads remote changes and merges them into your branch -- B) Downloads remote changes and updates remote-tracking branches only -- C) Pushes local changes to the remote -- D) Deletes remote branches that no longer exist - -**Q3.** What is the difference between `git pull` and `git fetch`? - -- A) They are identical commands -- B) `pull` only downloads; `fetch` also merges -- C) `pull` fetches and then merges; `fetch` only downloads -- D) `pull` works with tags; `fetch` works with branches - -**Q4.** Why might a `git push` be rejected? - -- A) The remote repository is read-only -- B) The remote branch has commits that your local branch does not have -- C) Your local branch is ahead of the remote -- D) You forgot to run `git add` first - -**Q5.** What is the advantage of `--force-with-lease` over `--force`? - -- A) It is faster -- B) It fails if someone else pushed since your last fetch -- C) It pushes all branches at once -- D) It creates a merge commit on the remote - -**Q6.** What does `git push -u origin feature` do that `git push origin -feature` does not? - -- A) It creates the branch on the remote -- B) It forces the push even if rejected -- C) It sets up tracking so future pushes need no arguments -- D) It pushes all branches at once - -**Q7.** In the forking workflow, what is the conventional name for the -original repository's remote? - -- A) origin -- B) source -- C) upstream -- D) base - -### Answers - -1. B — A local copy, an `origin` remote, and remote-tracking branches -2. B — Downloads remote changes and updates remote-tracking branches only -3. C — `pull` fetches and then merges; `fetch` only downloads -4. B — The remote branch has commits that your local branch does not have -5. B — It fails if someone else pushed since your last fetch -6. C — It sets up tracking so future pushes need no arguments -7. C — upstream diff --git a/astro-site/src/content/docs/subprojects.md b/astro-site/src/content/docs/subprojects.md deleted file mode 100644 index 2be2888..0000000 --- a/astro-site/src/content/docs/subprojects.md +++ /dev/null @@ -1,281 +0,0 @@ ---- -title: "Subprojects" -section: "subprojects" -order: 5 ---- - -## 1. Overview - -Projects often depend on code from other repositories — shared -libraries, frameworks, or configuration templates. Copying the code -manually means maintaining every copy independently. Git offers two -built-in solutions to include external repositories: **submodules** -and **subtrees**. - -In this chapter you will learn: - -- How submodules embed a reference to an external repository at a pinned commit -- How subtrees merge external repository content directly into your project -- When to use submodules versus subtrees based on your workflow - -## 2. Submodules - -A submodule is a reference to a specific commit in another repository. -Git stores only the URL and the commit hash — it does not copy the -files into the parent repository until you explicitly initialize and -update the submodule. - -![Submodules](../assets/images/git-submodules.png) - -### Adding a submodule - -```text -$ git submodule add https://github.com/user/lib.git -$ git commit -m "Add lib as submodule" -``` - -This creates two entries: -- `.gitmodules` — records the URL and path -- A special directory entry in the index pointing to the pinned commit - -After adding, the project looks like this: - -``` -project/ -├── .gitmodules ← URL and path for each submodule -├── .git/modules// ← submodule's Git database -├── / ← submodule files -│ └── ... -└── src/ -``` - -### Cloning a repository with submodules - -```text -$ git clone --recurse-submodules https://github.com/user/project.git -``` - -If you already cloned without `--recurse-submodules`: - -```text -$ git submodule update --init --recursive -``` - -### Updating a submodule - -```text -$ cd -$ git fetch origin -$ git switch main -$ git pull -$ cd ../.. -$ git add -$ git commit -m "Update lib submodule" -``` - -Or update all submodules at once: - -```text -$ git submodule update --remote -``` - -### Removing a submodule - -```text -$ git submodule deinit # unregister the submodule -$ git rm # remove from index and working tree -$ rm -rf .git/modules/ # clean up cached module data -$ git commit -m "Remove lib submodule" -``` - -### Trade-offs - -| Advantage | Drawback | -|-----------------------------------------|-----------------------------------------------------------| -| Native to Git — no extra tools | Requires extra commands (`submodule init`, `update`) | -| Small footprint — commit reference only | Contributors must remember to initialize after cloning | -| Each submodule has independent history | Nested submodules skipped by default (need `--recursive`) | -| Pin to a specific version | Merging changes back into the submodule is awkward | - -## 3. Subtrees - -A subtree is a full copy of another repository — files and history — -merged directly into a subdirectory of the parent project. Unlike -submodules, the files are part of the parent repository and can be -managed with standard Git commands. - -![Subtrees](../assets/images/git-subtrees.png) - -### Adding a subtree - -```text -$ git subtree add --prefix= https://github.com/user/lib.git main --squash -``` - -The `--squash` flag collapses the subtree's history into a single -commit, keeping the parent history clean. - -### Pulling updates - -```text -$ git subtree pull --prefix= https://github.com/user/lib.git main --squash -``` - -### Pushing changes back - -If you modify subtree files in the parent and want to push them back -to the original repository: - -```text -$ git subtree push --prefix= https://github.com/user/lib.git main -``` - -### Removing a subtree - -A subtree is just a directory — remove it like any other files: - -```text -$ git rm -r -$ git commit -m "Remove lib subtree" -``` - -Unlike submodules, there is no metadata to clean up. - -### Trade-offs - -| Advantage | Drawback | -|---------------------------------------------|----------------------------------------------------| -| No extra commands — files are in the repo | Increases repository size (full copy) | -| Works with standard `clone`, `pull`, `push` | Must not mix parent and subtree changes in commits | -| No `.gitmodules` or metadata files | Requires understanding of merge strategies | - -## 4. Which to use? - -| Aspect | Submodules | Subtrees | -|-------------------|-------------------------------|----------------------------------| -| Storage | Commit reference only | Full file copy | -| Contributor setup | Must run `submodule init` | Nothing extra | -| Update method | Manual (`submodule update`) | Standard (`subtree pull`) | -| Pin to version | Yes — by commit hash | No — always latest at pull time | -| Repo size impact | Minimal | Larger | -| Best for | Libraries pinned to a version | Frequently modified dependencies | - -Use **submodules** for component-based development where you depend on -a specific version of an external repository and rarely modify the -dependency. Use **subtrees** for system-based development where you -want a full copy of the code and expect to modify it alongside your -project. - -## 5. Other tools - -- [google repo](https://gerrit.googlesource.com/git-repo/) — manages - many Git repositories as a single project (used by Android) -- [git subrepo](https://github.com/ingydotnet/git-subrepo#readme) — - alternative to subtrees with cleaner UX - -## Exercises - -All exercises use the `concepts-lab` repository from previous chapters. - -### Exercise 1: Add and use a submodule - -**Task:** Add an external repository as a submodule, update it, and -verify the pinned commit. - -**Steps:** - -1. In `concepts-lab`, add a public repository as a submodule: - `git submodule add https://github.com/braboj/tutorial-testing.git libs/testing` -2. Run `git status` — note the new `.gitmodules` file and the `libs/testing` entry -3. Commit with the message `Add testing as submodule` -4. Run `cat .gitmodules` to see the URL and path -5. Run `git submodule status` to see the pinned commit hash -6. Enter `libs/testing` and run `git log --oneline -3` to see its history -7. Back in the parent, run `git diff --cached --submodule` to confirm the reference - -**Verify:** - -`.gitmodules` lists the submodule. `git submodule status` shows the -pinned commit hash. The submodule directory contains the external -repository's files. - -### Exercise 2: Clone a repository with submodules - -**Task:** Simulate a fresh clone and verify submodules need explicit -initialization. - -**Prerequisite:** `concepts-lab` must be pushed to GitHub (see -[Remote Repositories](../remote-repositories/), Exercise 1). - -**Steps:** - -1. Clone `concepts-lab` into a new directory without `--recurse-submodules`: - `git clone concepts-lab-fresh` -2. Enter `concepts-lab-fresh/libs/testing` — it should be empty -3. Run `git submodule update --init` -4. Check `libs/testing` again — files should now be present -5. Run `git submodule status` to confirm the correct commit is checked out - -**Verify:** - -Before `submodule update --init`, the directory is empty. After, it -contains the submodule's files at the pinned commit. - -### Exercise 3: Add a subtree - -**Task:** Add an external repository as a subtree and verify the files -are part of the parent repository. - -**Steps:** - -1. In `concepts-lab`, add a subtree: - `git subtree add --prefix=libs/docs https://github.com/braboj/tutorial-testing.git main --squash` -2. Run `git log --oneline -3` — note the squash merge commit -3. List `libs/docs/` to confirm the files are present -4. Run `git status` — the working tree should be clean (files are committed) -5. Edit a file in `libs/docs/`, commit the change -6. Run `git log --oneline -5` to see both the subtree add and your edit - -**Verify:** - -The subtree files are committed directly in the parent repository. -`git log` shows the squash merge and your edit as normal commits. -No `.gitmodules` file was created. - -## Quiz - -**Q1.** What does a submodule store in the parent repository? - -- A) A full copy of all files and history -- B) A URL and a pinned commit hash -- C) A compressed archive of the source code -- D) A symbolic link to another directory - -**Q2.** What happens when you clone a repository with submodules -without using `--recurse-submodules`? - -- A) Git refuses to clone -- B) The submodule directories exist but are empty -- C) Git automatically downloads all submodules -- D) The submodule entries are deleted - -**Q3.** What is the main advantage of subtrees over submodules? - -- A) Subtrees use less disk space -- B) Subtrees pin to a specific commit -- C) Contributors need no extra commands — files are already in the repo -- D) Subtrees support nested dependencies - -**Q4.** When should you prefer submodules over subtrees? - -- A) When you frequently modify the dependency -- B) When you want the dependency files in your repository -- C) When you need to pin to a specific version and rarely change the dependency -- D) When you want to avoid `.gitmodules` - -### Answers - -1. B — A URL and a pinned commit hash -2. B — The submodule directories exist but are empty -3. C — Contributors need no extra commands — files are already in the repo -4. C — When you need to pin to a specific version and rarely change the dependency diff --git a/astro-site/src/plugins/remark-rewrite-links.ts b/astro-site/src/plugins/remark-rewrite-links.ts new file mode 100644 index 0000000..a9acf63 --- /dev/null +++ b/astro-site/src/plugins/remark-rewrite-links.ts @@ -0,0 +1,15 @@ +import { visit } from "unist-util-visit"; +import type { Root, Link } from "mdast"; + +const CHAPTER_LINK = /^(\d{2}-)(.+)\.md(#.*)?$/; + +export function remarkRewriteLinks() { + return (tree: Root) => { + visit(tree, "link", (node: Link) => { + const match = node.url.match(CHAPTER_LINK); + if (match) { + node.url = `../${match[2]}/${match[3] || ""}`; + } + }); + }; +} diff --git a/docs/PLAYBOOK.md b/docs/PLAYBOOK.md index cf82ee5..a82606e 100644 --- a/docs/PLAYBOOK.md +++ b/docs/PLAYBOOK.md @@ -96,17 +96,12 @@ cd astro-site npm run dev # http://localhost:4321/tutorial-git/ ``` -### 3.2 Syncing chapters to the Astro site +### 3.2 Content source -Chapters in `chapters/` are the canonical source. The Astro site reads -copies from `astro-site/src/content/docs/`. After editing a chapter: - -1. Copy the file: `cp chapters/NN-slug.md astro-site/src/content/docs/slug.md` -2. Fix cross-references — replace `(NN-slug.md)` with `(../slug/)` -3. Verify the build: `npm run build` - -The filename in `content/docs/` drops the number prefix (e.g. -`01-introduction.md` → `introduction.md`). +Chapters in `chapters/` are the single source for the Astro site. No +manual sync is needed. Cross-references use `NN-slug.md` format; the +remark plugin in `astro-site/src/plugins/remark-rewrite-links.ts` +rewrites them to Astro-compatible paths at build time. ### 3.3 Building for production