mirror of
https://github.com/Sea-Haven-Industries/engineering-handbook.git
synced 2026-09-30 06:53:15 +00:00
Some checks failed
ci / ci / ci (push) Has been cancelled
Converted HCP repos use org reusables, dual OIDC claims, and a ci-complete ruleset instead of copied target jobs and Mergify.
184 lines
10 KiB
Markdown
184 lines
10 KiB
Markdown
# Git Workflow
|
|
|
|
## Branching
|
|
|
|
- Never commit directly to `main`
|
|
- Delete branches after merge
|
|
- Use the appropriate branch prefix for the type of work:
|
|
|
|
| Prefix | Use when |
|
|
|---|---|
|
|
| `feature/<description>` | Adding new functionality or enhancing existing features |
|
|
| `fix/<description>` | Fixing a non-urgent defect found during development or testing |
|
|
| `hotfix/<description>` | Fixing a production issue that needs immediate attention |
|
|
| `chore/<description>` | Routine maintenance, cleanup, dependency bumps, or tooling |
|
|
| `docs/<description>` | Documentation-only changes |
|
|
| `refactor/<description>` | Restructuring code without changing behavior |
|
|
| `release/<description>` | Preparing a release (version bump, tag, changelog) |
|
|
|
|
The prefixes mirror the commit types in [commit-messages.md](commit-messages.md#types), so the branch and its commits speak the same vocabulary. Use kebab-case for the description (e.g. `feature/add-receipt-parser`).
|
|
|
|
**fix vs hotfix:** Use `fix/` for defects caught before they affect production (failing tests, broken dev flows, issues found in review). Use `hotfix/` only when production is impacted and the fix needs to bypass normal review cadence.
|
|
|
|
**Jira key:** The Jira key does not go in the branch name. It goes in the PR title as a suffix (e.g. `feat(parser): add receipt parser (DEV-123)`). See [pull-requests.md](pull-requests.md#title) and [Linking to Jira](#linking-to-jira) below.
|
|
|
|
## Commits
|
|
|
|
- Commit each logical change individually with a descriptive message
|
|
- See [commit-messages.md](commit-messages.md) for formatting rules
|
|
- Keep the working tree clean: commit or stash before switching context
|
|
|
|
## Linking to Jira
|
|
|
|
Work is tracked in Jira (`seahaven.atlassian.net`). Jira is the source of truth for *work*; GitHub is the source of truth for *code*. We do not duplicate issues between the two — we link them.
|
|
|
|
The org-level **GitHub for Jira** app is already installed across all repos. It detects the Jira issue key (e.g. `DEV-123`) wherever it appears and surfaces the branch, commits, PR, and deployment status in that issue's development panel automatically. To make that happen:
|
|
|
|
- **Required:** include the key at the end of the PR title in parentheses — `feat(parser): add receipt parser (DEV-123)`. See [pull-requests.md](pull-requests.md#title) for the full format.
|
|
- **Recommended:** include the key in the `Refs:` trailer of each commit (see [commit-messages.md](commit-messages.md#referencing-issues)).
|
|
|
|
Branch names do not carry Jira keys. The required linkage point is the PR title.
|
|
|
|
### Smart Commit commands
|
|
|
|
The integration also accepts inline commands in commit messages to act on the issue without opening Jira:
|
|
|
|
| Command | Effect |
|
|
|---|---|
|
|
| `PROJ-123 #comment <text>` | Add a comment to the issue |
|
|
| `PROJ-123 #time 2h <text>` | Log work |
|
|
| `PROJ-123 #done` (or another transition name) | Transition the issue |
|
|
|
|
Transition names are case-insensitive and match the issue's workflow (`#in-progress`, `#done`). Use these sparingly; the link itself is the main goal, not driving the whole workflow from commits.
|
|
|
|
## Deploying
|
|
|
|
Nobody deploys from a workstation. The sanctioned paths depend on the lane. See [cicd.md](cicd.md).
|
|
|
|
**HCP Terraform (default).** Merge to `main` deploys **dev**: HCP auto-applies if `terraform/**` changed, and GitHub Actions deploys the app unless the merge was terraform-only. **Prod** is a human GitHub Release (`gh release create vX.Y.Z --target main --generate-notes`). That tag applies prod infra and queues the prod app deploys behind Environment reviewers and `ship-gate`. Rollback is `workflow_dispatch` of the deploy workflow at a prior tag, not a Terraform revert of application content.
|
|
|
|
A production break that cannot wait for `main` uses a hotfix branch cut from the last prod tag. CI runs on `hotfix/**` and `release/**`. Cut the next patch Release from that branch, approve prod after the HCP apply, then merge the hotfix branch into `main`. See [cicd.md](cicd.md#hotfix-ship-path). Do not add GitFlow trains or auto merge-back.
|
|
|
|
**SAM / CDK (remaining).** Merge to `main` (or `workflow_dispatch` of that same pipeline) is still the only path.
|
|
|
|
The standard flow for an HCP app repo:
|
|
|
|
1. Create a feature branch (`feature/add-receipt-parser`)
|
|
2. Develop and commit locally
|
|
3. Open a PR via `gh pr create`
|
|
4. Get the review, and confirm CI is green (`gh pr checks`). Speculative HCP plans cover both workspaces.
|
|
5. Merge the PR on GitHub
|
|
6. Confirm the dev apply (if terraform changed) and the dev app deploy. Infra and app may share a PR; if the app job raced the apply, re-run it.
|
|
7. Cut prod with `gh release create`. Approve the prod Environment gate after the HCP prod apply lands. Verify live state, not action success.
|
|
8. If prod is wrong, dispatch the deploy workflow at the previous tag and approve the gate.
|
|
|
|
Hotfix when prod is already broken:
|
|
|
|
```bash
|
|
git fetch --tags
|
|
git checkout -b hotfix/describe-the-break v1.2.3
|
|
# commit, push (CI on hotfix/**) or PR targeting the hotfix branch
|
|
gh release create v1.2.4 --target hotfix/describe-the-break --generate-notes
|
|
# approve prod Environment after the HCP prod apply
|
|
# merge hotfix/describe-the-break into main
|
|
```
|
|
|
|
A local deploy puts code into an environment that no reviewed commit describes, and its result depends on whoever ran it having the right credentials and a clean working tree. The pipeline deploys a known commit with the repo's own OIDC role every time.
|
|
|
|
### Legacy exception: deploy-then-merge
|
|
|
|
Merging the feature branch into `main` locally, deploying by hand (`sam deploy`, `cdk deploy`), and only then merging the PR on GitHub is a legacy pattern. It is not a default and it is not available on request. Both of these must hold:
|
|
|
|
- **The change is data-loss-prone and stateful.** A resource replacement that would recreate a table or bucket, a one-way schema or data migration, or a stack operation that has to be watched and aborted mid-flight, where an automated deploy or rollback could destroy data.
|
|
- **Adam has signed off on that specific deploy in advance.**
|
|
|
|
Write the justification into the PR description under `## Notes`: what the stateful risk is, and where the sign-off happened. An exception that is not written down is not an exception, so use the pipeline.
|
|
|
|
## Push Discipline
|
|
|
|
- Push regularly; don't accumulate unpushed commits
|
|
- Never force push `main`
|
|
- Never amend or rewrite commits already pushed to `main`
|
|
- If you need to add context to a past change, use PR descriptions or issue comments
|
|
|
|
## Versioning
|
|
|
|
Use [Semantic Versioning](https://semver.org/) (SemVer) as the standard: `MAJOR.MINOR.PATCH`.
|
|
|
|
| Increment | When |
|
|
|---|---|
|
|
| `MAJOR` | Breaking changes to the public API or contract (removed endpoints, changed request/response shapes, incompatible config changes) |
|
|
| `MINOR` | New functionality that is backwards-compatible (new endpoints, new optional fields, new features) |
|
|
| `PATCH` | Backwards-compatible bug fixes, dependency updates, documentation corrections |
|
|
|
|
### When to version
|
|
|
|
HCP app repos always cut SemVer tags. The tag is what applies prod infra and what the prod app deploy checks out.
|
|
|
|
| Always version | Don't version |
|
|
|---|---|
|
|
| HCP Terraform app repos (the prod trigger) | One-off scripts and migration tools |
|
|
| Published packages (npm, PyPI) | Remaining SAM/CDK stacks that still deploy only on merge to `main` |
|
|
| Public APIs with external consumers | |
|
|
| Shared libraries used across repos | |
|
|
| CLIs distributed to users | |
|
|
|
|
### How to tag
|
|
|
|
For HCP app repos, cut a GitHub Release from `main` after the PR is merged. Do not use a workflow. Do not `git tag` as the normal path (a tag without a Release does not fire `release: published`):
|
|
|
|
```bash
|
|
gh release create v1.2.0 --target main --generate-notes
|
|
```
|
|
|
|
A hotfix Release targets the hotfix branch instead of `main`. See [cicd.md](cicd.md#hotfix-ship-path).
|
|
|
|
For packages and libraries that are not HCP app repos, annotated tags remain fine:
|
|
|
|
```bash
|
|
git tag -a v1.2.0 -m "v1.2.0"
|
|
git push origin v1.2.0
|
|
```
|
|
|
|
Start at `v0.1.0` for new projects. Move to `v1.0.0` when the interface is stable.
|
|
|
|
## Git Hooks
|
|
|
|
The global security `pre-push` hook at `~/.config/git/hooks/pre-push` is the only pre-push hook in active use. It runs the workstation security scanner before every push and is the deterministic gate for catching secrets and high-severity findings locally. Linting (ruff, tsc/eslint) runs in CI, not in pre-push.
|
|
|
|
The [`hooks/`](hooks/) directory ships a `pre-push` shim for use by repos that set their own `core.hooksPath`. That shim re-execs the global hook rather than replacing it. It is not an npm-ci runner and is not a general-purpose pre-push template.
|
|
|
|
### `core.hooksPath` shadows, it does not merge
|
|
|
|
`core.hooksPath` names a single directory, not a search path. Setting it in a repo (for example to a tracked `.githooks/`) makes git run hooks *only* from there and ignore the machine-global hooks directory completely. Where that global directory holds a mandatory hook, such as the workstation security `pre-push`, a repo-local `core.hooksPath` silently disables it: no error, no output, no gate.
|
|
|
|
A repo that sets its own `core.hooksPath` must ship a `pre-push` that re-execs the global hook instead of replacing it:
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
GLOBAL="${SH_GLOBAL_HOOKS:-$HOME/.config/git/hooks}/pre-push"
|
|
if [ ! -x "$GLOBAL" ]; then
|
|
echo "pre-push: global security hook is missing or not executable: $GLOBAL" >&2
|
|
exit 1
|
|
fi
|
|
exec "$GLOBAL" "$@" # passes args and stdin, preserves the exit code
|
|
```
|
|
|
|
Two consequences:
|
|
|
|
- An installer that sets `core.hooksPath` must verify the shim delegates **before** it touches the config, and refuse to install if the delegation is missing or broken.
|
|
- A tracked hooks directory only exists on branches that contain it, so the gate is off on every branch that predates it. Land the hooks directory on all long-lived branches before installing.
|
|
|
|
Check what is actually in effect with `git config core.hooksPath` and `git config --local core.hooksPath`, then confirm the repo's `pre-push` still execs the global one.
|
|
|
|
## Cleaning Up
|
|
|
|
After a PR is merged:
|
|
|
|
```bash
|
|
git checkout main
|
|
git pull
|
|
git branch -d feature/add-receipt-parser
|
|
```
|
|
|
|
Don't let stale local branches or untracked project directories accumulate.
|