Odoo 19 modules by DonsWayo. One folder per module.
| Module | What it does | Version |
|---|---|---|
dw_git |
Git Hosting — Git repository hosting inside Odoo, documented below | 19.0.1.5.0 |
Git repository hosting inside Odoo 19.
Self-hosted repositories as first-class Odoo records — real bare repos on
disk, served over Git Smart HTTP, with pull requests, reviews, access tokens
and webhooks wired into Odoo's own users, groups and record rules.
git clone, git push and git fetch talk
straight to them through git http-backend.
Features · Install · Clone & push · Permissions · Testing · Limitations · Contributing
Status: working, self-hosted, single-node. Read docs/LIMITATIONS.md before deploying — notably webhooks are recorded but never delivered, there is no SSH transport, and branch protection is enforced on Odoo-side merges only, not on
git push.
| Area | Supported |
|---|---|
| Repositories | private (members only) and internal (all employees) visibility, owner + member + group access, stars, per-owner namespacing (<owner>/<name>.git) |
| Git transport | Smart HTTP clone / fetch / push via git http-backend, Basic-auth with Personal Access Tokens, proper 401 WWW-Authenticate challenges |
| Branches | synced from the bare repo on every push, ahead/behind counters, protection settings (see limitations), default-branch tracking |
| Commits | last 50 per branch mirrored into Odoo on push — SHA, author, message, date |
| Pull requests | draft → open → merged/closed, merge / squash / rebase, conflict detection, merges performed with merge-tree + commit-tree so they work on bare repos |
| Reviews | approve / request changes / comment, approval counting against the target branch's required count; a "request changes" blocks the merge |
| Tokens | PATs and deploy keys stored only as SHA-256 hashes, with scopes and expiry; the raw secret is shown once and never persisted |
| Webhooks | payload construction and HMAC-SHA256 signing, delivery records — not delivered, see limitations |
| Code browsing | read-only file browser — branch selector, directory-at-a-time tree, syntax-highlighted file contents |
| Diffs | pull request diffs rendered as a real coloured diff with syntax highlighting, plus the raw unified patch |
| Notifications | mail on PR created / review requested / merged / closed, and a to-do activity for each newly requested reviewer |
| Portal | repository, commit and pull request pages — see Two interfaces for who these are for |
| JSON-RPC API | repositories, branches, commits, tree, blob, pull requests, reviews |
| Translations | i18n/dw_git.pot template, regenerated with make i18n |
Not included: issues, wiki, labels, forks, SSH.
The module ships two separate UIs. They look nothing alike, which is expected — they serve different people.
| Backend | Portal | |
|---|---|---|
| URL | /odoo/… — the Git app |
/git/<owner>/<repo> |
| Audience | internal employees | portal users, who have no backend access |
| Look | standard Odoo backend | website theme (site logo, site footer) |
| Reached from | the Git app menu | links in notification emails |
Notification emails do not hardcode either one. They link through
_notify_get_action_link('view'), Odoo's own redirector, which resolves per
recipient: an employee lands on the backend record, a portal user on the
portal page. One link, right destination.
Caveat. The portal is not currently reachable in practice:
member_idsis restricted to internal users and no portal access rules ship with the module, so no portal user can legitimately be given a repository. The pages render, but the audience cannot be created. Tracked in #21 — the decision is whether to grant portal access properly or drop the portal.
Only four things live in the menu. Everything else — branches, commits, the file tree, diffs, reviews — is reached from inside a repository or a pull request. That is deliberate (those records only mean anything in the context of one repo), but it does mean the most interesting screens are not where people look first.
Git
├── Repositories ──────── list / kanban → open one
│ ├── header ▸ Browse Files ......... the file tree (branch selector,
│ │ directory-at-a-time, highlighted)
│ ├── header ▸ Stars
│ └── stat buttons ▸ Branches · Commits · Pull Requests
│ tabs ▸ Members · Settings · chatter
│
├── Pull Requests ─────── list → open one
│ ├── tabs ▸ Description
│ │ ▸ Changes ............... click any filename → diff dialog
│ │ (Diff tab = rendered + highlighted,
│ │ Raw Patch tab = git apply-able)
│ │ ▸ Reviews · Commits · Settings
│ └── header ▸ Merge · Squash & Merge · Rebase & Merge · Close
│ ▸ Refresh Changes ....... recompute the diff vs merge base
│
└── Configuration
├── Repository Base Path ........... where bare repos live on disk
├── Webhooks ....................... payloads are built and signed,
│ but not delivered (see Limitations)
├── Personal Access Tokens ......... clone/push over HTTP
└── Deploy Keys .................... per-repository, optional push
The two people miss most often:
- The file tree is the
Browse Filesbutton in the repository form header (folder icon), not a menu entry. It is a client action bound to the open repository, so it has no meaning — and no entry — outside one. - The diff is not on the PR form directly. Open the Changes tab and
click a filename; the diff opens in a dialog with
DiffandRaw Patchtabs. If it is empty, press Refresh Changes in the header — it will now tell you why it failed instead of silently doing nothing.
git clone https://github.com/DonsWayo/odoo-addons.git
cd odoo-addons
make build up installLog in at http://localhost:8069 — admin / admin. make help lists every
target; make versions prints what is actually running.
Stack: Odoo 19 · PostgreSQL 18 · GitPython 3.1.59 · mailpit (local mail).
make seed # 3 repositories with real git history
DW_GIT_RESET=1 make seed # ...rebuilt from scratchEvery repository this creates is a real bare git repository on disk with
real commits and branches, and its pull requests get their diffs through the
same code path a git push uses. That distinction matters: a repository
record with no git repository behind it shows changed files with line counts
and an empty diff — the UI describing a history that was never pushed. If a
diff does not render after seeding, that is a bug and not a gap in the
fixture.
Odoo queues mail and delivers nothing without an SMTP server, so notifications are invisible by default. The stack runs mailpit, which catches every outgoing message instead of sending it:
make mail # list what has been sent, with a link to the mailbox
make mail-clear # empty itOpen http://localhost:8025 to read the actual rendered message. make seed points Odoo at it automatically.
This is worth using rather than trusting the code: two notification bugs here
were invisible until the real message could be read — templates that rendered
their own source ({{ object.title }} reaching the recipient as text), and
mail addressed to nobody at all. Both passed every test that did not open the
mailbox.
The module source is baked into the image, so code changes need
make upgrade (rebuild + recreate + upgrade). To iterate without rebuilding,
enable the bind mount:
cp docker-compose.override.yml.example docker-compose.override.yml
docker compose up -d --force-recreate odooCopy dw_git/ into your addons path, update the apps list, install
Git Hosting. Requires the git binary on the server and
pip install GitPython.
Then set Settings → Technical → System Parameters:
| Key | Meaning | Default |
|---|---|---|
dw_git.repo_base_path |
where bare repos live | /var/lib/odoo/git/repos |
dw_git.ssh_host |
host shown in the (non-functional) SSH clone URL | git.example.com |
The Odoo process must own repo_base_path — it creates
<base>/<owner-login>/<repo-name>.git and runs git http-backend there.
Create a Personal Access Token under Git → Configuration → Access Tokens. The token is displayed once, on the form that creates it; it is stored only as a hash and cannot be recovered afterwards.
git clone https://<login>:<token>@your-odoo-host/git/<owner>/<repo>.git
cd repo && git push origin mainA push syncs branches and the last 50 commits per branch back into Odoo.
Access is decided in two independent places, and both must allow an action:
- Record rules (
security/record_rules.xml) govern the ORM and the UI. Every rule is scoped todw_git.group_git_user, whichbase.group_userimplies — so every internal employee is a Git User. git.repository._check_repo_access()governs the controllers (Smart HTTP, JSON-RPC, portal), which runsudo()searches and must decide access themselves.
A token never grants more than its owner already has: a PAT is resolved to its owning user, and that user's access to the repository is what gets checked.
make ci # the CI gates, on a throwaway database — trust this one
make check # xml + lint + upgrade + tests
make test # 291 tests: unit, real-git integration, HTTP, e2e, regression
make test-one T=TestJsonApi
make qa # 6 deterministic browser flows
make release-check # the full pre-tag gate, including a clean installdw_git/tests/test_e2e_git_lifecycle.py runs the loop the product exists
for: clone over HTTP with a real token, commit, push, watch the branches and
commits appear in Odoo, open a pull request, merge it, and confirm the bare
repository on disk agrees. test_regressions.py holds one test per defect
found in the August 2026 audit — see
docs/AUDIT-2026-08.md.
| docs/LIMITATIONS.md | What is deliberately absent, and why — read before deploying |
| docs/AUDIT-2026-08.md | August 2026 audit: every finding, its evidence, and the test that now covers it |
| docs/RELEASING.md | Versioning, pre-release checks, upgrade notes |
| docs/PUBLISHING.md | Odoo Apps Store checklist and where to tell people about it |
| docs/ROADMAP.md | What needs a rethink: dead integrations, design decisions, growing past one module |
| CHANGELOG.md | What changed, per release |
| CONTRIBUTING.md | Dev loop and the testing rules this project holds to |
| SECURITY.md | Reporting vulnerabilities; past advisories |
| AGENTS.md · odoo19-dev skill | Every Odoo 17→19 breaking change this module hit, for coding agents |
Issues and pull requests welcome — start with CONTRIBUTING.md. The short version: a new route needs an HTTP test, a new x2many needs a test that populates it, and a permission change needs a test acting as a second user. This project learned why the hard way.
Report vulnerabilities privately via GitHub Security Advisories, not public issues. See SECURITY.md — if you ever ran 19.0.1.0.0, rotate every access token and deploy key.
LGPL-3, same as Odoo. Copyright © 2026 Juan Jose Carracedo — see NOTICE.






