## What it is

Git is the version-control system every modern deployment workflow assumes. The `main` branch is the source of truth; tags are immutable release pointers; a `git push` to a configured remote is what kicks off the build pipeline, the SSH transport to your servers, and the atomic release swap that puts new code in front of users.

This sheet covers the commands and patterns you reach for when Git is part of a deployment system — not just the day-to-day clone/commit/push loop, but the tagging and branching conventions deploy tools key off, the `git archive` pattern that produces clean release tarballs, and the recovery commands (`reflog`, `restore`, `fsck`) that get you out of trouble when a force push or a stale `main` puts the wrong code in production.

## Quick reference

### Repository basics

```bash
git init                                            # new repo in cwd
git init --bare deploy.git                          # server-side bare repo (no working tree)
git clone git@github.com:org/app.git                # SSH clone (auth via deploy/personal key)
git clone https://github.com/org/app.git            # HTTPS clone (auth via token in URL or credential helper)
git clone --depth 1 git@github.com:org/app.git      # shallow clone — faster, no history
git clone --branch v1.4.2 --single-branch git@github.com:org/app.git

git status                                          # working tree state
git status -s                                       # short format (scriptable)
git status -b                                       # include branch info
git status --porcelain                              # machine-readable (stable across versions)

git log                                             # full history
git log --oneline -20                               # one-line summary, last 20
git log --graph --oneline --all --decorate          # branch topology
git log --since="2 weeks ago" --author=facundo
git log --grep="hotfix"                             # match commit message
git log -S "function_name"                          # pickaxe: find commits that touched the string
git log --follow path/to/file                       # history of one file through renames
git log <branch>..HEAD                              # commits on HEAD not on <branch>
```

`git status --porcelain` is the version that's safe to parse in deploy scripts. The human-readable `git status` output reformats across releases; `--porcelain` won't.

### Staging and committing

```bash
git add file.txt                                    # stage one file
git add -p                                          # interactive: choose hunks to stage
git add -A                                          # stage everything (incl. deletions)
git add -u                                          # only stage modified+deleted, not new

git restore --staged file.txt                       # unstage (modern equivalent of `git reset HEAD`)
git restore file.txt                                # discard unstaged changes (DESTRUCTIVE)

git commit -m "fix: handle null user agent"
git commit -am "..."                                # add tracked + commit in one step
git commit --amend                                  # rewrite last commit (NEVER after pushing to shared)
git commit --amend --no-edit                        # add files to last commit without editing message
git commit --fixup <SHA>                            # marker for an interactive autosquash later
git commit --allow-empty -m "trigger CI"            # empty commit (CI/deploy trigger pattern)

git diff                                            # unstaged changes
git diff --staged                                   # staged but not committed
git diff HEAD~5 HEAD -- path/                       # changes in last 5 commits under path/
git diff --stat                                     # summary (files + line counts)
git diff --check                                    # find whitespace errors before committing
```

### Branches

```bash
git branch                                          # list local
git branch -a                                       # include remotes
git branch -vv                                      # show upstream tracking
git branch --merged main                            # branches already merged into main (prune candidates)
git branch --no-merged main                         # branches NOT yet in main

git switch main                                     # check out main (modern alias for checkout)
git switch -c feature/login                         # create + switch
git checkout main                                   # older form, still works
git checkout -b feature/login

git merge feature/login                             # merge into current branch
git merge --no-ff feature/login                     # always create a merge commit
git merge --squash feature/login                    # collapse history of branch into one commit

git rebase main                                     # replay current branch on top of main
git rebase -i HEAD~5                                # interactive: edit/reorder/squash last 5
git rebase --abort                                  # bail out of a rebase mid-flight
git rebase --continue                               # after resolving conflicts
git rebase --skip                                   # skip current commit (DESTRUCTIVE if mid-merge)

git branch -d feature/login                         # safe delete (refuses if unmerged)
git branch -D feature/login                         # force delete (DESTRUCTIVE)
git push origin --delete feature/login              # delete remote branch
```

### Remotes

```bash
git remote -v                                       # list remotes
git remote add deploy git@deploy.example.com:app.git
git remote set-url origin git@github.com:org/app.git
git remote rename origin upstream

git fetch                                           # download new commits without merging
git fetch --all --prune                             # all remotes + remove deleted branches
git pull                                            # fetch + merge (or rebase if configured)
git pull --rebase                                   # fetch + rebase (cleaner history)

git push                                            # push current branch
git push -u origin feature/login                    # push + set upstream tracking
git push origin :feature/login                      # delete remote branch (old syntax)
git push --tags                                     # push annotated tags
git push --follow-tags                              # push + only the tags reachable from pushed commits
git push --force-with-lease                         # safer force push (see notes below)
git push --force                                    # nuclear force push (NEVER on shared branches)
```

**Never `--force` push to `main`.** `--force-with-lease` is the rule for force-pushing anywhere collaborators might be — it refuses to overwrite a remote that has new commits you haven't seen, preventing the classic "I just nuked Alex's work" failure.

### History rewriting

```bash
git rebase -i HEAD~5                                # interactive rebase: reorder, squash, edit, drop
git rebase -i --autosquash main                     # auto-apply --fixup commits during rebase
git cherry-pick <SHA>                               # apply one commit onto current branch
git cherry-pick <SHA1>..<SHA2>                      # apply a range
git cherry-pick -x <SHA>                            # include "cherry picked from..." in message

git reset --soft HEAD~1                             # uncommit, keep staged
git reset --mixed HEAD~1                            # uncommit, unstage (default)
git reset --hard HEAD~1                             # uncommit + discard changes (DESTRUCTIVE)
git reset --hard origin/main                        # nuke local main to match remote (DESTRUCTIVE)

git revert <SHA>                                    # create a new commit that reverses <SHA>
git revert -n <SHA>                                 # stage the revert without committing yet
git revert HEAD~3..HEAD                             # revert a range
```

`revert` is the production-safe choice — it adds a new commit that undoes the bad one, leaving history intact. `reset --hard` rewrites history, which only works on commits nobody else has pulled.

### Stash

```bash
git stash                                           # save unstaged + staged, clean working tree
git stash -u                                        # also include untracked files
git stash push -m "wip on login flow"               # named stash
git stash list                                      # show all stashes
git stash show -p stash@{0}                         # full diff of a stash
git stash pop                                       # apply top stash + remove from list
git stash apply stash@{1}                           # apply without removing
git stash drop stash@{0}
git stash clear                                     # nuke all stashes (DESTRUCTIVE)

git stash branch new-branch stash@{0}               # create a branch from a stash (recovers conflicts)
```

### Tags (releases)

```bash
git tag                                             # list tags
git tag -l 'v1.*'                                   # filter by pattern
git tag v1.4.2                                      # lightweight tag (just a ref)
git tag -a v1.4.2 -m "release v1.4.2"               # annotated tag (recommended for releases)
git tag -a v1.4.2 <SHA>                             # tag a specific commit
git tag -s v1.4.2 -m "..."                          # signed tag (GPG)
git tag -d v1.4.2                                   # delete local
git push origin v1.4.2                              # push one tag
git push origin --tags                              # push all tags
git push origin :refs/tags/v1.4.2                   # delete remote tag (rarely needed)

git describe --tags                                 # nearest tag + commits since (e.g. v1.4.2-3-gabc123)
git describe --tags --abbrev=0                      # just the nearest tag (for release builds)
```

Annotated tags carry tagger, date, message, and (optionally) a signature. Lightweight tags don't — they're just pointers. Production releases should always be annotated; you want the metadata when you `git show v1.4.2` six months later.

### Submodules

```bash
git submodule add git@github.com:org/lib.git vendor/lib
git submodule update --init --recursive             # initial fetch after clone
git submodule update --remote                       # advance submodule to its remote tip
git submodule foreach 'git pull origin main'        # bulk pull
git submodule deinit vendor/lib                     # un-register submodule
git rm vendor/lib                                   # remove submodule from repo
```

Deploy pipelines must `git clone --recurse-submodules` or `submodule update --init` after clone — without it, the submodule directories are present but empty, and your build silently picks up no code from them.

### Worktrees

```bash
git worktree add ../app-hotfix hotfix/auth-bug      # check out a branch in a separate dir
git worktree list                                   # show all worktrees
git worktree remove ../app-hotfix                   # clean up
git worktree prune                                  # remove stale worktree references
```

Worktrees let you have multiple branches checked out simultaneously without a second clone — useful for emergency hotfix work on production code while your main checkout is mid-refactor.

### Configuration

```bash
git config --global user.name "Facundo Farias"
git config --global user.email "facundo@example.com"
git config --global init.defaultBranch main         # new repos default to `main`, not `master`
git config --global pull.rebase true                # `pull` rebases instead of merging
git config --global push.autoSetupRemote true       # `push` on new branches sets upstream automatically
git config --global rerere.enabled true             # remember resolved conflicts; auto-resolve next time
git config --global core.autocrlf input             # macOS/Linux: never write CRLF
git config --global commit.gpgsign true             # sign every commit

git config --list                                   # all effective config
git config --list --show-origin                     # which file each setting came from
git config --get remote.origin.url
git config --unset alias.co                         # remove a setting
```

Per-repo config lives in `.git/config`; global in `~/.gitconfig`; system in `/etc/gitconfig`. The right level matters for shared CI runners — set CI-specific config per-repo, not globally.

### Inspecting history

```bash
git show <SHA>                                      # commit + diff
git show <SHA>:path/to/file                         # file at that commit (no checkout needed)
git show <SHA> --stat                               # just the file stats

git blame path/to/file                              # who changed each line, when
git blame -L 10,30 path/to/file                     # only lines 10-30
git blame -w path/to/file                           # ignore whitespace-only changes

git log -p path/to/file                             # patch history of one file
git log --all --source -- path/to/file              # find any branch that touched the file
git log --diff-filter=D -- path/to/file             # commits that DELETED the file

git shortlog -sn                                    # commits per author (sorted)
git shortlog -sne --since="last month"              # with email, recent only
```

### Recovery

```bash
git reflog                                          # every HEAD change in the last 90 days
git reflog show feature/login                       # reflog for one branch
git reset --hard HEAD@{2}                           # rewind HEAD to 2 reflog entries ago

git fsck --lost-found                               # find unreachable commits/blobs (orphaned after reset)
git cat-file -p <SHA>                               # raw object content

git restore --source=HEAD~3 path/to/file            # restore file from 3 commits ago, no reset
git restore --staged --worktree path/to/file        # both unstage AND discard changes

# Recover a deleted branch from reflog
git reflog | grep feature/login                     # find the SHA where it was
git branch feature/login <SHA>                      # recreate it pointing at that SHA
```

The reflog is the Get-Out-of-Jail-Free card. As long as the bad operation was local and within the last 90 days (default `gc.reflogExpire`), the previous HEAD is recoverable. If you've just run `reset --hard` and panicked: stop, `git reflog`, and you're probably fine.

---

## Deployment workflows (the moat)

The reference above is portable — every Git tutorial has those commands. The patterns below are the ones that matter once Git stops being a developer tool and becomes the trigger for production releases.

### 1. Branching strategy: production-first vs trunk-based

The branching model is a deploy decision, not a Git decision. Three patterns that actually hold up at small-to-medium team size:

**Trunk-based (recommended for ≤10 devs).** Everyone commits to `main`. Feature flags gate unfinished work. Releases tag a SHA on `main`. Pull requests are still mandatory; they're just short-lived.

```bash
git switch main
git pull
git switch -c facundo/feature-X
# ... work, commit ...
git push -u origin facundo/feature-X
# open PR, get review, merge into main
# tag a release when ready
git tag -a v1.4.2 -m "release v1.4.2"
git push --follow-tags
```

**GitFlow (medium teams with scheduled releases).** Long-lived `develop` branch holds work-in-progress; `main` holds production code; release branches stage what's about to ship; hotfix branches go straight off `main`.

**Production-environment branching (multi-tenant agencies, complex compliance).** A branch per environment (`staging`, `production`, `production-eu`). Deploys are merges into the environment branch. Useful when different environments must run different code for compliance reasons — overkill otherwise.

In DeployHQ's [for-agencies workflow](https://www.deployhq.com/for-agencies), environment-per-branch is the common pattern; for a single-product startup, trunk-based is almost always the right call. Whichever you pick, the [5 effective Git branching strategies](https://www.deployhq.com/blog/5-effective-git-branching-strategies-for-streamlined-development) post covers the trade-offs in more depth.

### 2. Tag-based releases

Production deploys should point at immutable tags, not at `main`. A tag pins a SHA forever; `main` moves with every push. Deploy pipelines that target `main` will silently ship the next merge while you're not looking.

```bash
# At release time:
git switch main
git pull
git tag -a v1.4.2 -m "release v1.4.2"   # annotated tag
git push --follow-tags                  # push commits + the tag

# DeployHQ (or any deploy tool) then targets the tag, not the branch
# e.g. configure the project to deploy `v1.4.*` tags automatically
```

Three properties that make tags the production-safe choice:

1. **Immutable by convention.** Pushing the same tag name twice requires `--force`, and most CI/CD tools reject it.
2. **Searchable in tooling.** `git describe` produces human-readable version strings (`v1.4.2-3-gabc123` = three commits past `v1.4.2`).
3. **Survives history rewrites.** A `git rebase` on `main` invalidates branch pointers; tagged commits keep their tags.

For semantic versioning workflows, the rule is `vMAJOR.MINOR.PATCH`. Pre-release builds use `v1.4.2-rc.1`; build metadata appends with `+sha-abc123`. Pick a convention and let your CI parse it — the convention matters more than which one you pick.

### 3. The `git archive` release artifact

The cleanest way to produce a release tarball for a deploy: `git archive`. It exports a tree at a specific SHA or tag, with no `.git/` directory and no working-tree noise:

```bash
# Produce a tarball of the v1.4.2 tag, no .git/, no untracked files
git archive --format=tar.gz --prefix=app-1.4.2/ v1.4.2 > app-1.4.2.tar.gz

# Same, from a remote without cloning (some hosts support this)
git archive --format=tar.gz --remote=git@github.com:org/app.git v1.4.2 > app-1.4.2.tar.gz

# Pipe directly over SSH to a deploy host
git archive --format=tar v1.4.2 | ssh deploy@host "tar -xf - -C /var/www/releases/v1.4.2"
```

Compared to `git clone` + `rm -rf .git`:

- **No `.git/`.** `.git/` on a long-lived repo is 50-500 MB. A clean `git archive` of the same tree might be 5-50 MB.
- **Honours `.gitattributes` `export-ignore`.** Mark `tests/`, `.github/`, and `docs/` as `export-ignore` and they're excluded from the tarball automatically.
- **Deterministic.** Same tag → identical bytes. Useful for build cache keys.

```gitattributes
# .gitattributes — checked into the repo
tests/      export-ignore
docs/       export-ignore
.github/    export-ignore
.gitignore  export-ignore
.editorconfig export-ignore
```

For atomic releases on the deploy host, pair `git archive` with the symlink-swap pattern: `tar -xf release.tar -C /var/www/releases/v1.4.2/`, then `ln -sfn /var/www/releases/v1.4.2 /var/www/current`. DeployHQ's [atomic deployments](https://www.deployhq.com/features/atomic-deployments) implements the same flow — but the underlying primitive is `git archive` + `tar` + `ln`.

### 4. Force-push safely: `--force-with-lease`

`git push --force` overwrites the remote with your local view, no questions asked. If a teammate pushed between your last fetch and your force, their work is gone. The fix is `--force-with-lease`:

```bash
# Safer force-push: refuses if the remote has new commits you haven't seen
git push --force-with-lease origin feature/login

# Even safer: specify the expected remote SHA
git push --force-with-lease=feature/login:<SHA-you-expect> origin feature/login
```

The mental model: `--force` says "make the remote match my local." `--force-with-lease` says "make the remote match my local, but only if the remote is currently where I last saw it." The second form catches the race condition that produces the lost-work incident.

Three rules that make force-pushing non-destructive in practice:

1. **Never force-push to `main`.** Configure GitHub/GitLab to reject it via branch protection rules — defense in depth.
2. **Always `--force-with-lease`, never `--force`.** Make the safe one your muscle memory.
3. **`git fetch` before every force-push** to feature branches. The lease is fresh; the surprise is smaller.

### 5. Atomic commits and conventional messages

A commit message convention isn't about aesthetics — it's about what your changelog generator, your CI, and your future-self can parse. The [Conventional Commits](https://www.conventionalcommits.org/) format is the de-facto standard:

```
feat(auth): add SAML SSO provider
fix(api): handle null user-agent in rate limiter
chore: bump deps
docs: update README deploy section

BREAKING CHANGE: removes /v1/users endpoint
```

Why this matters for deploys:

- **`feat` and `fix` bump SemVer differently** when fed into `semantic-release` or similar tooling.
- **`BREAKING CHANGE` triggers a major bump** and surfaces in release notes.
- **`chore`, `docs`, `test`, `refactor`** can be filtered out of changelogs automatically.

Pair this with `commit --fixup` and `rebase --autosquash` to keep PR history clean before merge:

```bash
# Notice a typo in commit abc123
git commit --fixup abc123          # marker commit
# ... continue working ...
# Before opening PR, squash the fixup into its target
git rebase -i --autosquash main    # interactive rebase auto-organises fixups
```

### 6. Hooks for pre-push checks

Local hooks catch the same class of errors that CI catches, but five seconds earlier and without burning runner minutes:

```bash
# .git/hooks/pre-push — runs before every git push
#!/usr/bin/env bash
set -euo pipefail

# Block pushes that include a stray console.log
if git diff --cached origin/main | grep -E '^\+.*console\.log'; then
  echo "ERROR: console.log in staged changes" >&2
  exit 1
fi

# Block pushes that include "FIXME" or "XXX"
if git diff --cached origin/main | grep -E '^\+.*(FIXME|XXX)'; then
  echo "ERROR: FIXME/XXX in staged changes" >&2
  exit 1
fi

# Run quick tests
npm test || exit 1
```

`chmod +x .git/hooks/pre-push` to enable. For team-wide hooks (since `.git/hooks/` isn't checked into the repo), use [husky](https://typicode.github.io/husky/), [lefthook](https://github.com/evilmartian/lefthook), or [pre-commit](https://pre-commit.com/) — they install hooks from a checked-in config file so every clone gets them automatically.

### 7. `git bisect` for production-incident triage

When a deploy ships a regression, `git bisect` finds the breaking commit in O(log n) — a 200-commit range takes 7-8 bisection steps:

```bash
git bisect start
git bisect bad                                      # current SHA is broken
git bisect good v1.4.0                              # v1.4.0 was fine
# Git checks out a SHA halfway between
# Run your reproducer:
./reproduce-bug.sh && git bisect good || git bisect bad
# Git checks out a new midpoint; repeat
# Eventually:
# abc123def is the first bad commit
git bisect reset                                    # return to where you started
```

For deploy regressions specifically: `git bisect` between the last good tag and the broken one, and the reproducer is your smoke test. Within 5-10 minutes you have a one-line answer to "what shipped that broke prod?"

### 8. Deploy-key auth from CI

CI runners and production hosts must authenticate to Git with deploy keys, never personal keys. A deploy key:

- Is generated on the runner/host, never copied from a laptop.
- Is scoped to one repo (GitHub) or one host (GitLab).
- Is read-only by default — only repos that need write access (release-tagger bots) get write.

```bash
# On a fresh CI runner: generate a per-repo deploy key
ssh-keygen -t ed25519 -f ~/.ssh/deploy_myapp -N "" -C "ci-deploy:myapp"
cat ~/.ssh/deploy_myapp.pub
# Upload that public key to GitHub: Settings → Deploy keys → Add deploy key

# Configure ~/.ssh/config so `git clone` picks the right key
cat >> ~/.ssh/config <<'EOF'
Host github-myapp
    HostName github.com
    User git
    IdentityFile ~/.ssh/deploy_myapp
    IdentitiesOnly yes
EOF

# Clone via the host alias
git clone git@github-myapp:org/myapp.git
```

The [SSH cheatsheet](https://www.deployhq.com/cheatsheets/ssh) has the full key-rotation playbook for the moment one of these keys is compromised or an employee leaves.

---

## Common errors and fixes

| Error / symptom | Cause | Fix |
|---|---|---|
| `fatal: refusing to merge unrelated histories` | Two repos with no common ancestor | `git pull --allow-unrelated-histories` (rare — usually means you cloned the wrong repo) |
| `error: failed to push some refs to ...` (non-fast-forward) | Remote has commits you don't have | `git pull --rebase` then push again |
| `error: Your local changes to the following files would be overwritten by merge` | Uncommitted changes block the merge | `git stash`, `git pull`, `git stash pop` (resolve any conflicts after pop) |
| `fatal: Not possible to fast-forward, aborting` | `pull.ff=only` is set and remote diverged | `git pull --rebase` or `git merge --no-ff` depending on convention |
| `Permission denied (publickey)` on push/clone | Wrong SSH key being offered | `ssh -T git@github.com` to confirm auth; add `IdentitiesOnly yes` and the right `IdentityFile` to `~/.ssh/config` |
| `remote: Repository not found` (with valid key) | The key authenticated as a different user who can't see the repo | Check `ssh -T git@github.com` — the auth message shows whose account; switch keys via `~/.ssh/config` host alias |
| Detached HEAD warnings | You checked out a tag or SHA, not a branch | `git switch -c branch-name` to start tracking work, or `git switch main` to leave |
| `fatal: ambiguous argument 'main': unknown revision` | Default branch is still `master` | Either `git switch master` or rename: `git branch -m master main && git push -u origin main` |
| Force-push wiped a teammate's commits | Used `--force` instead of `--force-with-lease` | Recover from reflog: `git reflog` to find the lost SHA, `git push origin <SHA>:refs/heads/<branch>` to restore |
| `git pull` overwrote my changes | Local changes lived in working tree; pull's merge committed over them | Recover from reflog: `git reflog` → `git reset --hard HEAD@{1}` (your pre-pull state) |
| Submodule directory empty after clone | `--recurse-submodules` not passed | `git submodule update --init --recursive` |
| `error: cannot lock ref ...: ref ... is at A but expected B` | Stale local ref vs remote (often after a force-push upstream) | `git fetch --prune origin` to clean up |
| `Auto-merging X CONFLICT (content): Merge conflict in X` | Two branches edited the same lines | Resolve conflict markers in the file, `git add X`, `git rebase --continue` or `git commit` |
| `fatal: bad object HEAD` | Corrupted `.git/HEAD` (rare) | `git fsck` to assess damage; restore from `.git/logs/HEAD` or re-clone if irrecoverable |
| Push hangs forever on a large repo | Git LFS misconfigured or large binary in history | `git lfs ls-files` to verify; if a binary slipped into history, `git filter-repo --strip-blobs-bigger-than 50M` |
| `error: GH001: Large files detected` from GitHub | A single file >100MB pushed without LFS | Install Git LFS, rewrite the offending commit: `git lfs migrate import --include="*.bin"` |
| Tags pushed by accident, need to remove | `git push --tags` shipped a draft tag | `git push origin :refs/tags/v1.4.2-draft` to delete remote tag |
| Wrong author on commits in a long branch | `user.email` was unset or wrong | `git rebase -i --exec 'git commit --amend --no-edit --author="Name <email>"' main` to rewrite all authors |

---

## Companion: full DeployHQ deploy workflow

A real Git-based deploy is a webhook from your Git host, a build pipeline that tests + builds the artifact, an SSH transport that ships it to your servers, and an atomic symlink swap that puts new code in front of users. DeployHQ wires this together so you don't script the orchestration yourself.

The end-to-end flow:

1. Push to `main` on GitHub or GitLab, or tag a `vX.Y.Z` release.
2. DeployHQ's webhook fires and pulls the SHA from your repo.
3. The [build pipeline](https://www.deployhq.com/features/build-pipelines) runs your install/build/test steps in a clean container, produces the release artifact, and caches `node_modules` / `vendor/` between deploys.
4. DeployHQ SSHes into your servers and runs your deploy hook against the new release directory.
5. The `current` symlink swaps atomically — [zero-downtime](https://www.deployhq.com/features/zero-downtime-deployments) by construction.
6. If the smoke test fails, [one-click rollback](https://www.deployhq.com/features/one-click-rollback) restores the previous SHA in one filesystem operation.

Setup walkthroughs: the [deploy from GitHub guide](https://www.deployhq.com/deploy-from-github) covers the GitHub side end-to-end, and the [deploy from GitLab guide](https://www.deployhq.com/deploy-from-gitlab) covers GitLab. Both end with the same atomic-release pattern under the hood.

For teams comparing Git-based deploys to CI-platform deploys, [What Is GitOps?](https://www.deployhq.com/blog/what-is-gitops) covers the principles, and the [DeployHQ vs GitHub Actions](https://www.deployhq.com/compare/deployhq-vs-github-actions) comparison goes deeper on the trade-offs.

[Start a free DeployHQ trial](https://www.deployhq.com/signup) to wire Git into a production deploy pipeline.

---

## Related cheatsheets

- [SSH cheatsheet](https://www.deployhq.com/cheatsheets/ssh) — for the deploy keys and `~/.ssh/config` aliases every `git clone` from CI depends on.
- [Bash scripting cheatsheet](https://www.deployhq.com/cheatsheets/bash) — for the `set -euo pipefail` deploy hooks that wrap `git archive` + `tar` + `ln`.
- [Docker cheatsheet](https://www.deployhq.com/cheatsheets/docker) — for the build pipelines that turn a `git archive` into a SHA-tagged container image.
- [rsync cheatsheet](https://www.deployhq.com/cheatsheets/rsync) — for the file-transport pattern that `git archive | tar | ssh` replaces on simple deploys.
- [Cheatsheets hub](https://www.deployhq.com/cheatsheets) — every DeployHQ cheatsheet in one place.

---

Need help? Email [support@deployhq.com](mailto:support@deployhq.com) or follow [@deployhq on X](https://x.com/deployhq).
