1
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Contributing to Fossilrepo
2
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
3
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Thanks for your interest in contributing. This document covers how to get set up, our coding standards, and the PR process.
4
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
5
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Development Setup
6
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
7
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Prerequisites
8
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
9
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Python 3.12+
10
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Docker and Docker Compose
11
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- [uv](https://docs.astral.sh/uv/) (Python package manager)
12
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- [Ruff](https://docs.astral.sh/ruff/) (linter/formatter)
13
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
14
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Running Locally
15
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
16
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```bash
17
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
git clone https://github.com/ConflictHQ/fossilrepo.git
18
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
cd fossilrepo
19
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
20
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Start infrastructure
21
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
docker compose up -d postgres redis mailpit
22
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
23
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Install dependencies
24
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
uv sync --all-extras
25
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
26
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Run migrations and seed data
27
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
DJANGO_DEBUG=true uv run python manage.py migrate
28
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
DJANGO_DEBUG=true uv run python manage.py seed
29
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
30
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Start the dev server
31
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
DJANGO_DEBUG=true POSTGRES_HOST=localhost uv run python manage.py runserver
32
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
33
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
34
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Or use Docker for everything:
35
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
36
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```bash
37
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
docker compose up -d --build
38
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
docker compose exec backend python manage.py migrate
39
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
docker compose exec backend python manage.py seed
40
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
41
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
42
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Default Users
43
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
44
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- `admin` / `admin` — superuser, full access
45
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- `viewer` / `viewer` — read-only permissions
46
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
47
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Code Style
48
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
49
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
We use **Ruff** for linting and formatting. No debates, no custom configs.
50
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
51
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```bash
52
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Check
53
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ruff check .
54
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ruff format --check .
55
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
56
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Fix
57
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ruff check --fix .
58
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ruff format .
59
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
60
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
61
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Key conventions:
62
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
63
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **Max line length:** 140 characters
64
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **Imports:** sorted by Ruff (isort rules)
65
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **Quote style:** double quotes
66
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **Target:** Python 3.12+
67
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
68
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Codebase Conventions
69
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
70
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Read [`bootstrap.md`](bootstrap.md) before writing code. It covers:
71
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
72
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Model base classes (`Tracking`, `BaseCoreModel`)
73
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Soft deletes (never call `.delete()`)
74
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Permission system (`P` enum + project-level RBAC)
75
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- View patterns (HTMX partials, auth checks)
76
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Template conventions (dark theme, Tailwind classes)
77
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
78
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Testing
79
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
80
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Tests run against a real PostgreSQL database. No mocked databases.
81
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
82
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```bash
83
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Run all tests
84
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
DJANGO_DEBUG=true uv run pytest
85
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
86
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Run specific test file
87
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
DJANGO_DEBUG=true uv run pytest tests/test_releases.py
88
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
89
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Run with coverage
90
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
DJANGO_DEBUG=true uv run pytest --cov
91
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
92
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
93
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Every PR should:
94
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
95
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Include tests for new features (happy path + permission denied cases)
96
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Not decrease test coverage
97
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Pass all existing tests
98
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
99
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Pull Request Process
100
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
101
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
1. **Fork and branch** from `main`. Branch naming: `feature/short-description` or `fix/short-description`.
102
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
103
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
2. **Write code** following the conventions in `bootstrap.md`.
104
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
105
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
3. **Write tests.** Both allowed and denied permission cases. Assert against database state, not just status codes.
106
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
107
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
4. **Lint and test locally.** CI will catch it anyway, but save yourself a round trip.
108
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
109
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
5. **Open a PR** with a clear description:
110
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- What changed and why
111
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- How to test it
112
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Link to any related issues
113
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
114
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
6. **Address review feedback** in new commits (don't amend/squash during review).
115
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
116
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
7. **Merge** when CI is green and review is approved.
117
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
118
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Reporting Issues
119
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
120
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Use [GitHub Issues](https://github.com/ConflictHQ/fossilrepo/issues). Include:
121
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
122
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- What you expected to happen
123
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- What actually happened
124
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Steps to reproduce
125
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Browser/OS/version if relevant
126
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
127
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Architecture Decisions
128
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
129
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Fossilrepo has some non-obvious design choices worth understanding:
130
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
131
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **No Fossil HTTP server.** We read `.fossil` files directly via SQLite (`FossilReader`) and use `fossil http` in CGI mode for sync. No persistent Fossil process, stateless containers.
132
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **Django-backed forum posts** supplement Fossil's native forum because Fossil forum posts don't sync via clone/pull.
133
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **Encrypted fields** use Fernet (AES-128-CBC + HMAC) keyed from `SECRET_KEY` for SSH keys and OAuth tokens at rest.
134
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **Single org model.** Multi-org is possible but not implemented — fossilrepo targets self-hosted single-team deployments.
135
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
136
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## License
137
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
138
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
By contributing, you agree that your contributions will be licensed under the MIT License.
139
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!