Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions chapters/07-playbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ For definitions, see [Glossary](09-glossary.md).
|--------|-----------------|
| [Remote Operations](playbook/remote-operations.md) | Push, pull, force push safely, sync forks |
| [Remote Management](playbook/remote-management.md) | Add, rename, remove remotes, switch URL, SSH setup |
| [SSH Setup](playbook/ssh-setup.md) | Key generation, agent, GitHub registration, troubleshooting |

## Project Structure

| Recipe | What you'll find |
|--------|-----------------|
| [Tagging](playbook/tagging.md) | Create, push, and delete annotated tags |
| [Submodules](playbook/submodules.md) | Add, clone, update, and remove submodules |
| [Remove a Submodule](playbook/remove-submodule.md) | Step-by-step cleanup — deinit, git rm, cached data |
| [Subtrees](playbook/subtrees.md) | Add, pull, push, and remove subtrees |

## Advanced
Expand All @@ -53,5 +55,8 @@ For definitions, see [Glossary](09-glossary.md).
|--------|-----------------|
| [Selectors](playbook/selectors.md) | Tilde, caret, double-dot, triple-dot, reflog refs |
| [Hooks](playbook/hooks.md) | Pre-commit, commit-msg, sharing hooks, bypassing |
| [Pre-commit Hook](playbook/pre-commit-hook.md) | Script creation, common checks, sharing and bypassing |
| [Commit-msg Hook](playbook/commit-msg-hook.md) | Message validation, Conventional Commits, examples |
| [Debugging](playbook/debugging.md) | Bisect, blame, and search commit history |
| [Git Bisect](playbook/git-bisect.md) | Binary search for the commit that introduced a bug |
| [Configuration](playbook/configuration.md) | Identity, defaults, aliases, and diagnostics |
55 changes: 55 additions & 0 deletions chapters/recipes/commit-msg-hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: "Commit-msg Hook"
description: "How to create a Git commit-msg hook that validates or enforces commit message conventions."
section: "playbook/commit-msg-hook"
order: 90
---

## Commit-msg Hook

A commit-msg hook runs after you write the commit message but before
the commit is finalized. It receives the path to a temporary file
containing the message as its first argument (`$1`). If the script
exits non-zero, the commit is aborted.

### Create the hook

```text
$ cat > .git/hooks/commit-msg << 'EOF'
#!/bin/sh
# Enforce minimum message length
if [ $(wc -c < "$1") -lt 10 ]; then
echo "Error: commit message too short"
exit 1
fi
EOF
$ chmod +x .git/hooks/commit-msg
```

### Common validations

- **Minimum length** — reject messages that are too terse to be
meaningful.
- **Conventional Commits format** — check that the message matches
`type(scope): description` using a regex.
- **Ticket reference** — require a Jira/Linear/GitHub issue key
(e.g. `PROJ-123`) in the message.

### Example: enforce Conventional Commits

```text
$ cat > .git/hooks/commit-msg << 'EOF'
#!/bin/sh
pattern="^(feat|fix|docs|chore|refactor|test|ci|style)(\(.+\))?: .{3,}"
if ! grep -qE "$pattern" "$1"; then
echo "Error: message must follow Conventional Commits format"
exit 1
fi
EOF
$ chmod +x .git/hooks/commit-msg
```

### Sharing and bypassing

Same as other hooks — see the [Hooks](hooks.md) recipe for
`core.hooksPath` and `--no-verify`.
16 changes: 2 additions & 14 deletions chapters/recipes/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,8 @@ order: 80

### Find which commit introduced a bug

```text
$ git bisect start
$ git bisect bad # current commit has the bug
$ git bisect good <hash> # 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 <good-hash>
$ git bisect run ./test.sh
```
Use [Git Bisect](git-bisect.md) for a full walkthrough of manual and
automated binary search through commit history.

### See who last changed each line

Expand Down
57 changes: 57 additions & 0 deletions chapters/recipes/git-bisect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: "Git Bisect"
description: "How to use git bisect to find the exact commit that introduced a bug, with manual and automated workflows."
section: "playbook/git-bisect"
order: 88
---

## Git Bisect

Bisect performs a binary search through your commit history to find
which commit introduced a bug. Instead of checking each commit
one-by-one, it halves the search space at every step.

### Manual bisect

```text
$ git bisect start
$ git bisect bad # current commit has the bug
$ git bisect good <hash> # this older commit was fine
```

Git checks out a midpoint commit. Test it, then mark it:

```text
$ git bisect good # midpoint is clean
$ git bisect bad # midpoint has the bug
```

Repeat until Git identifies the first bad commit, then clean up:

```text
$ git bisect reset # return to original branch
```

### Automated bisect with a test script

If you have a script that exits 0 for good and non-zero for bad,
bisect can run unattended:

```text
$ git bisect start HEAD <good-hash>
$ git bisect run ./test.sh
```

Git runs the script at each midpoint and reports the first bad
commit when done.

### Tips

- The good and bad commits do not have to be on the same branch —
bisect works across the entire reachable history.
- If a midpoint commit cannot be tested (e.g. broken build), use
`git bisect skip` to move past it.
- Use `git bisect log` to replay or share a bisect session.

For blame, log search, and other debugging tools, see the
[Debugging](debugging.md) recipe.
33 changes: 4 additions & 29 deletions chapters/recipes/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,13 @@ order: 83

### Create a pre-commit hook

```text
$ cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
# Reject commits that contain TODO
if git diff --cached --quiet -S "TODO"; then
exit 0
fi
echo "Error: commit contains TODO"
exit 1
EOF
$ chmod +x .git/hooks/pre-commit
```

The hook runs before every commit. A non-zero exit code aborts the
commit.
See [Pre-commit Hook](pre-commit-hook.md) for a full walkthrough —
script creation, common checks, and sharing with the team.

### Create a commit-msg hook

```text
$ cat > .git/hooks/commit-msg << 'EOF'
#!/bin/sh
# Enforce minimum message length
if [ $(wc -c < "$1") -lt 10 ]; then
echo "Error: commit message too short"
exit 1
fi
EOF
$ chmod +x .git/hooks/commit-msg
```

Receives the commit message file as `$1`. Useful for enforcing
message conventions.
See [Commit-msg Hook](commit-msg-hook.md) for a full walkthrough —
message validation, Conventional Commits enforcement, and examples.

### Share hooks with the team

Expand Down
58 changes: 58 additions & 0 deletions chapters/recipes/pre-commit-hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: "Pre-commit Hook"
description: "How to create a Git pre-commit hook that validates staged changes before every commit."
section: "playbook/pre-commit-hook"
order: 89
---

## Pre-commit Hook

A pre-commit hook runs automatically before every commit. If the
script exits with a non-zero code, the commit is aborted. Use it to
catch problems early — linting errors, TODO markers, large files,
or secrets.

### Create the hook

```text
$ cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
# Reject commits that contain TODO
if git diff --cached --quiet -S "TODO"; then
exit 0
fi
echo "Error: commit contains TODO"
exit 1
EOF
$ chmod +x .git/hooks/pre-commit
```

### How it works

1. You run `git commit`.
2. Git executes `.git/hooks/pre-commit` before opening the message
editor.
3. If the script exits 0, the commit proceeds. Any other exit code
aborts it.

### Common checks

- **Lint staged files** — run your linter on `git diff --cached --name-only`
output.
- **Detect secrets** — search for API keys, tokens, or passwords in
the diff.
- **Enforce file size limits** — reject files above a threshold.

### Sharing pre-commit hooks

Hooks in `.git/hooks/` are local and not committed. To share them
with the team, see the [Hooks](hooks.md) recipe for the
`core.hooksPath` approach.

### Bypassing

```text
$ git commit --no-verify -m "WIP: skip hooks"
```

Use sparingly — hooks exist for a reason.
14 changes: 13 additions & 1 deletion chapters/recipes/rebasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,29 @@ $ git rebase main

### Squash commits with interactive rebase

Squashing combines multiple commits into one, keeping history clean
before merging a feature branch.

```text
$ git rebase -i HEAD~3
# change "pick" to "squash" for commits to combine
# editor opens — change "pick" to "squash" (or "s") for commits to fold
# save and close — a second editor opens to combine the messages
```

If conflicts arise during the squash, resolve them and run
`git rebase --continue`.

### Abort a conflicted rebase

Restores the branch to its exact state before the rebase started.
Use this when conflicts are too tangled to resolve cleanly.

```text
$ git rebase --abort
```

Any conflict resolutions you made during the rebase are discarded.

### Continue after resolving a rebase conflict

```text
Expand Down
12 changes: 3 additions & 9 deletions chapters/recipes/remote-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,6 @@ $ git remote set-url origin git@github.com:<user>/<repo>.git

### Set up SSH authentication

```text
$ ssh-keygen -t ed25519 -C "you@example.com"
$ eval "$(ssh-agent -s)"
$ ssh-add ~/.ssh/id_ed25519
$ ssh -T git@github.com # verify connection
```

Add the public key (`~/.ssh/id_ed25519.pub`) to your GitHub account
under Settings > SSH Keys.
See [SSH Setup](ssh-setup.md) for a full walkthrough — key
generation, agent configuration, GitHub registration, and
troubleshooting.
45 changes: 45 additions & 0 deletions chapters/recipes/remove-submodule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: "Remove a Submodule"
description: "Step-by-step guide to fully removing a Git submodule — deinit, git rm, and cleaning up cached module data."
section: "playbook/remove-submodule"
order: 92
---

## Remove a Submodule

Removing a submodule is not a single command — it requires three
cleanup steps to fully clear the submodule from your repository.

### Steps

```text
$ git submodule deinit <path> # 1. unregister from .git/config
$ git rm <path> # 2. remove from working tree and index
$ rm -rf .git/modules/<path> # 3. delete cached clone
$ git commit -m "Remove submodule"
```

### What each step does

1. **`deinit`** — removes the submodule entry from `.git/config` and
clears the working directory at `<path>`. The submodule's URL
remains in `.gitmodules` until the next step removes it.
2. **`git rm`** — removes the submodule entry from `.gitmodules` and
from the index (staging area). Also deletes the working directory
if `deinit` did not already.
3. **`rm -rf .git/modules/<path>`** — deletes the cached bare clone
that Git keeps under `.git/modules/`. Without this step, re-adding
a submodule at the same path can use stale data.

### Common gotchas

- **Forgetting step 3** — the cached clone stays behind. If you later
add a submodule at the same path, Git may reuse the old checkout
and you get confusing state.
- **Uncommitted changes in the submodule** — `deinit` will refuse to
run. Use `--force` if you are certain the changes are not needed.
- **Nested submodules** — if the submodule itself contains submodules,
you need to deinit recursively or clean up `.git/modules/` manually.

For other submodule operations, see the [Submodules](submodules.md)
recipe.
Loading
Loading