Git workflow
Branch model, naming, merge policy, Conventional Commits with the scopes of both repositories, hooks, short-lived branches and Prisma migrations — the same rules in hoco and hoco-mobile.
Branch model
| Branch | Created from | Merges into | Lifetime | Deployed to | Merged by |
|---|---|---|---|---|---|
master | — | — | Permanent | Production, from tags vX.Y.Z | Repository owner only |
dev | master (once) | Cut into release/* | Permanent; the default branch | Staging, on every merge | PR author, after the review rules |
feature/<TASK-ID>-<desc> | dev | dev (squash) | ≤ 3 working days | Preview, where one exists | Author |
fix/<TASK-ID or issue>-<desc> | dev, or release/x.y.z during acceptance | Where it came from (squash) | ≤ 1 working day as a target | — | Author |
chore/<desc>, docs/<desc> | dev | dev (squash) | Short | — | Author |
release/x.y.z | dev | master (merge commit), then back into dev | Until x.y.z is in production | Pre-production, on tags vX.Y.Z-rc.N | Repository owner |
hotfix/x.y.z | The production tag on master | master (merge commit), then back into dev and any open release/* | Hours | Production | Repository owner |
The repository owner is Anis for hoco and Imad for hoco-mobile. Dependabot opens its own dependabot/… branches into dev.
Naming
Format: <type>/<TASK-ID>-<short-description>.
typeis one offeature,fix,chore,docs,release,hotfix.- The task id is written exactly as in the plan (uppercase:
SRV-14,MDA-05). A bug without a task uses its issue number (fix/57-…). - The description is 2 to 5 lowercase ASCII words in kebab-case; the whole name stays under 50 characters.
release/andhotfix/carry the version only.
| Branch name | Verdict |
|---|---|
feature/SRV-14-crm-module | OK |
feature/MDA-05-visit-flow (hoco-mobile) | OK |
fix/SRV-16-cancel-releases-stock | OK |
fix/57-checkout-rounding | OK — issue #57, no task |
docs/plan-week-update | OK |
release/1.0.0, hotfix/1.0.1 | OK |
anis-work, feature/crm, SRV-14, feature/SRV-14_CRM_Module | Rejected — no type, no task id, no description, or wrong case |
Merge policy
- Into
dev: squash merge. The PR title becomes the commit subject ondev, so the PR title must itself be a valid Conventional Commit with the task id. The body keepsCloses #n. release/x.y.zintomaster: merge commit (never squash), then the repository owner tags the merge commit with an annotated tag (git tag -a v1.0.0 -m "HOCO 1.0.0") and opens the back-merge PRmaster→dev, merged with a merge commit.- Fixes during acceptance branch from
release/x.y.zand merge back into it. The repository owner mergesrelease/x.y.zintodevafter each-rc.Ntag, sodevnever lacks a fix. hotfix/x.y.z: merge commit intomaster, tag, then back intodev(and into an openrelease/*, if any).- Syncing your own branch: rebase on
origin/dev, thengit push --force-with-lease. Never rebasedev,master,release/*, or a branch someone else has pushed to. Never mergedevinto a feature branch, except when two people share it. - No cherry-picks between
devand a release branch, except a hotfix that must also reach an open release branch (git cherry-pick -x, to keep the reference).
A feature, end to end:
git switch dev && git pull --ff-only
git switch -c feature/SRV-14-crm-module
# … work, commit in small steps …
git fetch origin && git rebase origin/dev # at least daily
git push -u origin feature/SRV-14-crm-module # first push
git push --force-with-lease # after a rebase
gh pr create --base dev --title "feat(server): reseller lifecycle transitions [SRV-14]"Conventional Commits
Format: <type>(<scope>): <subject> [TASK-ID]
- The subject is imperative, starts lowercase, has no final period. The whole header is at most 100 characters (commitlint
header-max-length). - The task id goes at the end in brackets:
[SRV-16], or[SRV-16, SRV-17]for two tasks. It is omitted only for chores and bugs without a task. - The body says why when it is not obvious. Footers:
Closes #42,Refs #57,BREAKING CHANGE: …. - Breaking changes add
!after the scope. For the API contract, a breaking change also follows the deprecation process — it is never shipped in place.
| Type | Use for |
|---|---|
feat | A new capability for a user or a consumer of the API |
fix | A bug fix |
perf | A performance improvement with no behaviour change |
refactor | Restructuring with no behaviour change |
test | Tests only |
docs | Documentation only (docs pages, the plan, comments) |
style | Formatting only |
build | Build system, Dockerfile, dependencies of the build |
ci | GitHub Actions workflows |
chore | Maintenance: dependency bumps, tooling, regenerated code |
revert | Reverting an earlier commit |
Scopes in hoco:
| Scope | Covers |
|---|---|
server | apps/server (api and worker) |
dashboard | apps/dashboard (back-office) |
web | apps/web (reseller web) |
docs | apps/docs — the site and its pages |
plan | apps/docs/content/plan/tasks.json — always in its own commit |
contracts | packages/contracts, and API reference pages aligned with it |
db | packages/db — schema, migrations, seeds |
api-client | packages/api-client |
ui | packages/ui |
shared | packages/shared |
env | packages/env, .env.example |
config | packages/config, root tooling (Biome, Turborepo, TypeScript, lefthook) |
docker | docker/, docker-compose.yml |
deps | Dependency bumps (Dependabot uses it) |
release | Version bumps and changelogs |
Scopes in hoco-mobile:
| Scope | Covers |
|---|---|
reseller | apps/reseller |
delegate | apps/delegate |
core | packages/hoco_core |
api | packages/hoco_api (regenerations) |
ui | packages/hoco_ui |
l10n | packages/hoco_l10n |
offline | packages/hoco_offline |
maps | packages/hoco_maps |
catalog | packages/hoco_catalog |
workspace | Root pubspec.yaml, Melos scripts, analysis options |
android, ios | Platform folders, signing, store configuration |
deps, release | As in hoco |
commitlint enforces the format and the type. The scope list is enforced in review: since dev only receives squash commits, the PR title is what lands, and the reviewer renames an unknown scope before merging.
feat(contracts): add delegate visit check-in operations [SRV-03]
feat(server): implement cart.checkout with stock and credit checks [SRV-15]
fix(server): release reserved stock when an order is cancelled [SRV-16]
feat(server): after-sales claims and returns with automatic restock [SRV-20]
feat(db): add Tour, Visit and VisitReport models [SRV-21]
feat(dashboard): orders-to-confirm queue [DSH-06]
feat(dashboard): returns and after-sales pages [DSH-13]
docs(plan): mark SRV-03 done, re-estimate SRV-21
chore(deps): bump next to 16.4.3
refactor(env)!: rename S3_BUCKET to S3_BUCKET_PUBLIC
BREAKING CHANGE: update your local .env from .env.example.feat(delegate): offline visit check-in with GPS tolerance [MDA-05]
feat(reseller): live order status and return requests [MRA-10]
fix(core): retry once after refreshing an expired session [MOB-02]
chore(api): regenerate client from contract 1.4.0 [MOB-03]Commit hygiene
- One concern per commit. Schema and contract commits come before the code that uses them.
- Stage explicitly (
git add <paths>) and readgit diff --stagedbefore committing. - Generated files are committed only where listed: the OpenAPI document (
apps/docs/public/openapi/hoco-v1.json), Prisma migrations, the team-task pages (pnpm docs:generate),hoco_apiand thebuild_runneroutputs inhoco-mobile, lockfiles. Never committed:.env, the Prisma client (prisma/generated),.next,dist, APK/AAB files, keystores, service-account files. - Lockfiles change only with a dependency change; CI installs with
--frozen-lockfile. - No commented-out code, no stray
console.logorprint, noTODOwithout an issue number (TODO(#57): …). - Line endings are LF everywhere (
.gitattributes:* text=auto eol=lf). On Windows, also setgit config --global core.autocrlf input.
Hooks
| Hook | hoco (lefthook, installed by pnpm install) | hoco-mobile (lefthook, set up in MOB-01) |
|---|---|---|
pre-commit | Biome check --write on staged files, fixes re-staged; gitleaks protect --staged when gitleaks is installed | dart format on staged Dart files; gitleaks |
commit-msg | commitlint (@commitlint/config-conventional, header ≤ 100) | The same commitlint configuration, so both repositories reject the same messages |
- Never commit with
--no-verify. A wrong hook is fixed in a PR. - Hooks missing? Run
pnpm exec lefthook install. Check with a commit message likewip: it must be rejected. - Install gitleaks locally (Scoop:
scoop install gitleaks). The hook skips it when absent, but CI scans every PR anyway.
Keep branches short
- Three working days at most from first commit to merge. A PR without activity for two working days is mentioned in the stand-up.
- A task longer than two days ships as several PRs under the same task id, each leaving
devdeployable. Typical order: contract → schema and migration → endpoints → back-office or app screens. - Unfinished work lands hidden: operations not yet called, back-office routes not in the navigation, app screens behind a release flag (feature flags). Anything visible but unfinished is behind a flag before the next release cut.
- Rebase on
devat least once a day, and right away when a contract or migration PR lands.
Database migrations in branches
The schema is multi-file Prisma in packages/db/prisma/schema/; migrations live in packages/db/prisma/migrations/.
Change the schema on your branch.
Create one migration named after the task and topic, in snake_case:
pnpm --filter @hoco/db exec prisma migrate dev --name srv14_crm_core
# review or edit the SQL before applying (renames, backfills):
pnpm --filter @hoco/db exec prisma migrate dev --create-only --name srv14_crm_coreCommit the schema and the migration folder together: feat(db): reseller lifecycle tables [SRV-14], with the label migration on the PR.
Keep one migration per PR. While iterating, delete your own unmerged migration folders, reset your local database (prisma migrate reset) and create a single migration again before opening the PR.
If dev gained a migration while your branch was open: rebase, delete your migration folder, prisma migrate reset, then recreate your migration so it sits after theirs.
Rules:
- A migration that has reached
devis never edited. Fix forward with a new one. - Expand, then contract. The new
apicontainer applies migrations when it starts, while the previous version may still be serving (architecture). A migration must therefore work with the code currently in production: add nullable columns and new tables first; rename, drop or setNOT NULLin a later release, after a backfill. - Destructive statements (
DROP,ALTER COLUMN … TYPE,SET NOT NULL, a unique index on a populated table) are read line by line by the reviewer, and the PR explains the plan. - Reference data (wilayas, roles and permissions, settings defaults, reference lists) changes through idempotent seeds, never through manual SQL on an environment.
pnpm db:pushis for a throwaway local database only, never for anything that will be committed.
The same rules in hoco-mobile
| Topic | hoco | hoco-mobile |
|---|---|---|
| Branch model | As above | Identical: master, dev, feature/MDA-05-visit-flow, release/1.0.0, hotfix/1.0.1 |
| Task ids | SRV, DSH, WEB, OPS, PRJ, and QA / LCH for server-side work | MOB, MRA, MDA, and QA / LCH for mobile work |
| Merge policy | Squash into dev; merge commits into master | Same |
| Tags | vX.Y.Z (server, back-office and web share one version); contracts-vX.Y.Z for the contract | vX.Y.Z — both apps share one version line; an app that did not change is not uploaded |
| Hooks | Biome, gitleaks, commitlint | dart format, gitleaks, commitlint |
| Generated code | OpenAPI document, team-task pages | hoco_api, build_runner outputs (freezed, json_serializable, drift) — committed, CI fails if regeneration changes them |
| Schema migrations | Prisma, expand then contract | drift in hoco_offline: every change bumps schemaVersion with a tested migration step. The outbox must survive every app update — a lost outbox is lost field data |
| Build numbers | — | versionName from the tag, versionCode = CI run number (always increasing) |
Repositories and access
The two repositories, what goes where, who can do what, branch protection, secrets, the accounts production depends on, and onboarding and offboarding checklists.
Pull requests and reviews
What a pull request must contain, how big it may be, the CI checks that gate it in each repository, who reviews what in a team of three, how fast, and how a PR is merged and cleaned up.