Git discipline and collaboration standards for DevRail-managed repositories. These complement the Conventional Commits section in DEVELOPMENT.md and the security scanning enforced by gitleaks.
- Never push directly to
main(ormaster). All changes reach the default branch through a pull/merge request. No exceptions. - Create feature branches from
main. Always branch from the latestmainto minimize merge conflicts. - Delete branches after merge. Stale branches clutter the repo. Configure your platform to auto-delete merged branches.
type/short-description
Use the same type prefixes as Conventional Commits:
| Type | Example |
|---|---|
feat |
feat/add-ansible-support |
fix |
fix/shellcheck-false-positive |
docs |
docs/update-python-standards |
chore |
chore/bump-ruff-version |
ci |
ci/add-arm64-build |
refactor |
refactor/simplify-log-library |
test |
test/add-bats-coverage |
Rules:
- Use lowercase with hyphens -- no underscores, no camelCase
- Keep descriptions short (2-4 words)
- Include issue number when applicable:
fix/123-login-error
Every change to main goes through a pull request (GitHub) or merge request (GitLab):
- Descriptive title -- follows conventional commit format:
type(scope): description - Summary -- explain what changed and why. Link to the issue being addressed.
- Test plan -- describe how the change was tested. Include commands to reproduce.
- Small, focused PRs -- one logical change per PR. Large PRs are harder to review and more likely to introduce bugs.
DevRail template repos ship with PR/MR templates. Use them. They include:
- Summary section
- Test plan checklist
- Standards compliance checklist (
make checkpasses, conventional commits used)
Use draft/WIP PRs for:
- Work in progress that needs early feedback
- Changes that are not yet ready for review
- Experimental approaches you want to discuss before investing more time
- Minimum 1 approval required before merging to
main. - No self-merge. The author does not approve their own PR. (Exception: solo maintainers on small repos may self-merge after CI passes.)
- Review within 24 hours. Unreviewed PRs block progress. If you cannot review in time, say so and suggest another reviewer.
- Re-review after significant changes. If a review round produces substantial rework, request a fresh review rather than relying on the original approval.
| Area | What to Look For |
|---|---|
| Correctness | Does the code do what the PR claims? Are edge cases handled? |
| Tests | Are new behaviors covered by tests? Do existing tests still pass? |
| Security | No secrets, no injection vulnerabilities, no unsafe deserialization, proper input validation |
| Standards | Follows DevRail conventions (naming, logging, idempotency, error handling) |
| Documentation | Public APIs documented, README updated if user-facing behavior changed |
| Simplicity | Could this be simpler? Is there unnecessary complexity or premature abstraction? |
- Comment on the code, not the author. "This function could be split" not "you wrote a messy function."
- Distinguish between blocking issues and suggestions. Prefix optional feedback with "nit:" or "suggestion:".
- Approve when satisfied. Do not hold PRs hostage over style preferences already covered by automated tooling.
Each commit represents one logical change:
- A bug fix is one commit (not three: "try fix", "actually fix", "fix typo in fix")
- A feature may be multiple commits if it has distinct, meaningful steps
- Refactoring and behavior changes are separate commits, even if related
All commits follow the format defined in DEVELOPMENT.md Conventional Commits. This section does not duplicate those rules -- refer there for types, scopes, and format.
- Never
--force-pushto shared branches (main,develop, release branches). This rewrites history that others depend on. - Force push is acceptable on your own feature branches to clean up history before review (interactive rebase, squash fixups).
- If you must force push a shared branch due to an emergency (e.g., removing an accidentally committed secret), coordinate with the team first.
- Squash-merge feature branches into
mainfor a clean, linear history. The squashed commit message should follow conventional commit format. - Individual commits on feature branches do not need to be perfectly formatted, but should still be meaningful (no "WIP" or "asdf" commits in the final PR).
Configure branch protection on main (or master) in every repository:
| Setting | Value |
|---|---|
| Require pull request before merging | Yes |
| Required approvals | 1 minimum |
| Require status checks to pass | Yes (make check / CI pipeline) |
| Require branches to be up to date | Yes |
| Allow force pushes | No |
| Allow deletions | No |
Platform-specific configuration:
- GitHub: Configure via Settings > Branches > Branch protection rules
- GitLab: Configure via Settings > Repository > Protected branches
Squash and merge (default). This produces one clean commit per feature on main:
- The squash commit message uses conventional commit format
- Individual feature branch commits are preserved in the PR history
- This keeps
mainhistory readable and bisectable
For branches that track upstream or parallel development (e.g., release branches):
- Rebase to incorporate changes from
main - Merge commits when syncing back to
main(to preserve the branch context)
- Resolve merge conflicts locally, not in the platform UI
- After resolving, run
make checkto verify the resolution did not break anything - If conflicts are extensive, consider splitting the PR into smaller pieces
- No secrets in commits. No API keys, passwords, tokens, or credentials in source code -- ever. This is enforced by
gitleaksin pre-commit hooks andmake scan. - No
.envfiles committed. Add.envto.gitignore. Use.env.examplewith placeholder values to document required variables. - Use platform-provided secret management. GitHub Secrets, GitLab CI/CD Variables, or a dedicated secrets manager (Vault, AWS Secrets Manager, etc.).
- If a secret is accidentally committed, rotate the secret immediately. Do not rely on
git rebaseto remove it -- the secret is already in reflog and may be in clones.
Every repository must have a .gitignore that excludes at minimum:
- IDE/editor files (
.idea/,.vscode/,*.swp) - OS artifacts (
.DS_Store,Thumbs.db) - Build artifacts and caches (
__pycache__/,.terraform/,*.tfstate) - Environment files (
.env,.env.local) - Dependency directories (
node_modules/,.venv/)
Signed commits (GPG or SSH) are recommended but not required. If your organization requires commit signing, configure it at the platform level via branch protection rules.
- Do not open public issues for security vulnerabilities
- Use the platform's private security advisory feature (GitHub Security Advisories, GitLab confidential issues)
- Follow the project's
SECURITY.mdif one exists
- Branch protection settings should be configured as part of repository setup, using the DevRail template repos as a baseline.
- The
gitleakspre-commit hook catches most secret leaks before they reach the remote. See Universal Security Tools for configuration. - The
make checkpre-push hook runs the full DevRail check suite before everygit push, providing a local safety net in addition to CI. Skip withgit push --no-verifywhen necessary. - For commit message format details, see Conventional Commits in DEVELOPMENT.md. This document intentionally does not duplicate those rules.