All posts
Developer8 min read

Managing OCA modules with git-aggregator: reproducible Odoo addon stacks

git-aggregator lets you declare all your Odoo addon sources - OCA, private repos, forks - in a single YAML file and reproduce the exact stack on any machine. Here is how to set it up and integrate it into CI.

Every production Odoo deployment ends up with the same problem: addons from multiple sources. The core Odoo modules live in the official Docker image. A handful of OCA modules are cloned from GitHub. One module is a fork with a patch your team has not yet upstreamed. Another is a private repo.

Six months later, nobody is sure which OCA commit is running in production. The fork has diverged from upstream and nobody tracked the merge base. CI uses different OCA versions than staging uses.

git-aggregator solves this by declaring all addon sources in a single YAML file, fetching everything reproducibly, and producing a merged addons directory.

Installing git-aggregator#

bash
pip install git-aggregator

The tool is maintained by the OCA and is the standard way OCA project maintainers manage their own multi-repo addon stacks.

The repos.yml file#

yaml
# repos.yml
./addons:
  remotes:
    oca: https://github.com/OCA
    origin: git@github.com:your-org

  merges:
    # OCA server-tools: pin to a specific commit for reproducibility
    - oca/server-tools 18.0 a1b2c3d4

    # OCA account: pin to a tag
    - oca/account-financial-tools 18.0.1.0.0

    # Your private addon repo
    - origin/my-custom-addons main

    # OCA module + your patch on top
    - oca/sale-workflow 18.0
    - origin/sale-workflow-patches main

  target: origin merged-addons

  # Only copy specific modules from the merged tree
  fetch:
    # From oca/server-tools
    - base_setup_partner
    - base_technical_user

    # From oca/account-financial-tools
    - account_cash_basis_base_account

The merges section declares remote + branch/ref pairs. git-aggregator fetches each one and merges them in order using git merge --no-edit. Conflicts must be resolved in your patch branches before adding them.

The fetch section limits which module directories are copied to the final addons path. Without it, every module in every cloned repo ends up in your addons directory - including dozens you do not need and that slow down Odoo's addon scan.

Running the aggregator#

bash
# Aggregate all sources and produce the ./addons directory
gitaggregate -c repos.yml

# With verbose output to see what is being fetched
gitaggregate -c repos.yml -v

# Only aggregate a specific target
gitaggregate -c repos.yml -p ./addons

The result is a single ./addons directory containing exactly the modules listed in fetch, from the exact commits specified.

Pinning to commits for reproducibility#

The 18.0 branch references are not pinned - they move as OCA merges PRs. For production stability, pin to a specific commit hash:

yaml
merges:
  - oca/server-tools 18.0 a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

Run this to find the current HEAD of an OCA branch and pin it:

bash
git ls-remote https://github.com/OCA/server-tools.git 18.0 | cut -f1

Update the hash whenever you deliberately want to pull in new OCA changes. This gives you explicit control over when OCA updates land in your addons directory.

CI integration#

Add the aggregation step before your Odoo tests run:

yaml
# .github/workflows/ci.yml
- name: Aggregate OCA modules
  run: |
    pip install git-aggregator
    gitaggregate -c repos.yml

- name: Run Odoo tests
  run: |
    # ./addons now contains all declared modules
    docker compose run --rm odoo odoo       --init all --database test --stop-after-init --test-enable

Commit repos.yml to your repo. Do not commit the ./addons directory itself (add it to .gitignore). The YAML file is the source of truth; the aggregated directory is a build artifact.

Handling merge conflicts#

When two OCA modules both patch the same base file (common with accounting modules), git-aggregator will fail on the merge. The correct fix is to create an intermediate branch in your private repo that applies the patches in the correct order, then reference that branch:

yaml
merges:
  - oca/account-financial-tools 18.0
  - oca/account-payment 18.0
  - origin/account-conflict-resolution main  # your branch resolving the conflict

Do not resolve conflicts by editing files in the aggregated ./addons directory - those changes will be lost on the next aggregation run.

Lockfile for exact reproducibility#

git-aggregator does not produce a lockfile by default. For maximum reproducibility - identical bytewise addons on every CI run and every developer machine - record the resolved hashes after aggregation:

bash
gitaggregate -c repos.yml
# After aggregation, export the resolved state
cd .git/aggregator-work
for repo in */; do
  echo "$repo: $(git -C $repo rev-parse HEAD)"
done > ../../repos.lock

Commit repos.lock to your repo. During CI, you can verify that the aggregation matches the lockfile or fail the build if it does not.

When not to use git-aggregator#

git-aggregator is the right tool when you have more than three OCA modules and need to track their versions. For a single OCA module or two, a simpler approach works fine: add the module directory to your own repo as a git submodule pinned to a specific commit. Submodules are more widely understood and have better IDE integration. The tradeoff is that managing multiple submodules for large OCA stacks becomes unwieldy - that is the point where git-aggregator pays off.

Try ERPeek on your own Odoo module - ask questions, scaffold tests, and explore your codebase in plain language.

Get started free