[17.0][ADD] project_git, project_github, project_gitlab connector - #1792
Open
FrancescoBallerini wants to merge 81 commits into
Open
[17.0][ADD] project_git, project_github, project_gitlab connector#1792FrancescoBallerini wants to merge 81 commits into
FrancescoBallerini wants to merge 81 commits into
Conversation
…ined in ir.config_parameter
…instead of making validations for each method
… already linked with gitlab MR, remove Title from template
…with task/ticket not found message
…in _notify_get_action_link method
Allow to link more than one merge request to task or project. Allow to see in Odoo status of merge request and pipeline
…equest and ci status
…ction is from expected gitlab server
- Replace the PR/branch/commit tabs on project.task with 3 smart buttons opening the filtered list views - Add search views for git.branch, git.commit and git.pull.request and hook them to the menu actions - Show related git entities as clickable embedded lists (readonly) instead of many2many tags on the git entity forms - Rename view xml_ids and names to the standard patterns (view_git_branch_form / view_git_branch_search...; inherited views named <base view>.inherit.webhook.gitlab) Assisted-by: Claude Fable 5
A GitLab MR opened with no commits yet (e.g. source branch identical to the target) carries last_commit: null in the webhook payload: the event processing crashed with a TypeError while preparing the pull request values. Default the last commit SHA to an empty string instead. Assisted-by: Claude Fable 5
Add 24 tests on top of the state-based suite, covering the code areas the suite bypassed so far: - controller authorization (test_controller.py, HttpCase with real requests): GitLab token and GitHub HMAC signature validation, missing or insecure authorization_token sysparam, queue job enqueueing, silent skip of events without a handler; - MR/PR state transitions and task tags: merge, approval, WIP, close; - GitLab pipeline: CI tag replacement on the task, non-success statuses, no update on head SHA mismatch; - matching/config edge cases: invalid custom regex falling back to the default, broken taskid# reference outside PR titles, repository matching via git_dev_project_url and .git suffix variants; - GitHub push symmetry (branch deletion, unmapped repository) and res.users mapping via gitlab_username/github_username. Assisted-by: Claude Fable 5
The git.request -> git.event rename left create_project_webhook and retry_odoo_sh_deploy_job pointing to the dropped model, so both buttons on the project form crashed. Besides the rename fix: - subscribe the deployed hook to push events too (the module now tracks commits and branch creation/deletion from push events) - tolerate a trailing .git in the configured project URL when deriving the GitLab API project path - document the deployment button in the readme Covered by tests/test_project.py (mocked GitLab API): hook payload, replacement of an existing hook, deploy on both project URLs, and the odoo_sh_deploy retry paths. Assisted-by: Claude Fable 5
create_project_webhook now dispatches on the URL host: github.com repositories get a web hook via the GitHub API (push and pull request events, json payload, authorization token as HMAC secret - the same signature the controller authorizes), any other host is treated as a GitLab instance as before. Same replace-then-create semantics on both platforms. The Odoo.sh job retry stays GitLab-only and now skips github.com URLs instead of querying them with the GitLab client. Assisted-by: Claude Fable 5
Rename the _create_or_update_{commit,branch,pull_request} helpers to
_get_or_create_*: callers use them as getters of the tracked entity,
with the update_existing flag making the default refresh behavior
explicit.
Move out of git.event the helpers unrelated to event processing:
- git.utils (new AbstractModel): task matching configuration and
explicit task id reference extraction;
- git.auth (new AbstractModel): authenticated GitLab/GitHub API
clients, so that git.pull.request and project.project no longer
reach into the event processor to get an API connection.
Assisted-by: Claude Fable 5
…tests Assisted-by: Claude Fable 5
This refactoring paves the way for a potential split of the connector into a platform-agnostic base module plus one bridge module per platform: once every platform-specific behavior is reached through an explicit dispatch seam, moving the implementations into their own modules becomes an almost purely mechanical operation. Platform-specific behavior is now reached through a single explicit dispatch pattern (if hasattr: getattr, as in delivery_carrier) instead of scattered if/else on the event source: - git.event._dispatch_by_source routes calls to <method>_<source> implementations (warn and skip when missing) and serves the optional per-source hooks of the generic methods (commit vals, PR identifiers, PR opening detection). - Platform implementations are uniformly named with a source suffix (_extract_branch_names_from_event_<source>, _fetch_pr_commits_<source>, _prepare_pull_request_vals_<source>, _post_message_<source>). - The controller recognizes the event source from the platform-claimed headers (_detect_event_source) and delegates the token check to _verify_webhook_token_<source>; request parsing is a pure dispatch. - Parsers normalize the event type onto the module-owned project_git_event_type key (replacing the GitLab-specific object_kind), and push events are refined into their concrete type (branch_creation, branch_deletion, commit_push) so that the key always names the git.event handler to invoke. - project.project._get_url_platform replaces _is_github_url (github claims its host, any other host is treated as a GitLab instance). - No more implicit "gitlab" defaults: the source is always explicit. Assisted-by: Claude Fable 5
…ader GitHub carries the event type in the X-GitHub-Event request header, not in the payload: the previous parser guessed the type by probing payload keys (pull_request / pusher), which misclassifies events that embed a pull_request object without being pull request events (e.g. pull_request_review, delivered by hooks subscribed to every event) and crashes their processing jobs. Request headers are now handed down to the source parsers (_parse_git_request_data(event, headers)): the GitHub parser maps the header - the platform's authoritative discriminator - onto project_git_event_type, so event types without a handler (ping, reviews, comments...) are skipped cleanly. The GitLab parser keeps reading object_kind, its own authoritative discriminator, from the payload and ignores the headers. Assisted-by: Claude Fable 5
GitHub delivers tag pushes as regular push events (X-GitHub-Event: push, ref refs/tags/...): a pushed tag whose name or commit messages referenced a task was classified as a branch creation and tracked as a git.branch record named "refs/tags/..." linked to the task. GitLab is not affected: its tag pushes carry their own object_kind (tag_push), which has no handler. The GitHub parser now remaps push events whose ref is not a branch onto the tag_push type, which no handler picks up - mirroring the GitLab behavior. The test dispatch helper skips handlerless event types the way the controller does. Assisted-by: Claude Fable 5
When the API commit fetch failed, the GitHub fallback fabricated a
head commit dict whose message embedded the PR title ("HEAD commit
from PR: ..."): it added no matching information (the title is
already a matching source of its own) and it created git.commit
records with made-up name/description - systematically so on
deployments without a GitHub token, where every fetch fails.
The payload-based fallback is now an optional per-source hook
(mandatory=False): GitLab keeps it (the MR payload carries the real
head commit in last_commit), GitHub simply does not implement it, and
GitHub PR commit tracking relies on the API fetch alone.
Assisted-by: Claude Fable 5
Three changes to _find_matching_tasks: - replace the pattern×project×task Python scan (which loaded every task of the mapped projects and ran a regex on each name) with an ORM search: project mapping + name ilike prefilter on the database side, then a Python refinement on the few candidates with the whole-word case-insensitive check that ilike cannot express. The ilike prefilter is a superset of the whole-word match, so no true match can be lost - hardcode the issue key pattern (Jira-strict \b[A-Z][A-Z]+-\d+\b), dropping the task_name_match_regex system parameter and its install-time seeding: a freely configurable regex was a foot-gun (broken or surprising patterns might be reported as module malfunctions) and required defensive handling in the matching code. The pattern stays overridable by inheriting git.utils._get_task_name_match_regex - match tasks whatever their state: the previous scan walked project.task_ids, which only carries open tasks, so keys referencing a closed task were silently ignored. Late commits on a task already marked as done are a common human pipeline slip, and still worth linking Assisted-by: Claude Fable 5
Drop the readonly flags from the git.branch / git.commit / git.pull.request form views: system administrators (who already have full access rights on the git models) can now create entities and adjust their task links by hand, e.g. to record a commit whose message forgot the task reference. For regular users nothing changes: without write access the forms stay readonly anyway. Assisted-by: Claude Fable 5
…_gitlab Split the connector into a platform-agnostic base module plus one bridge module per platform (project_github, project_gitlab), each providing its platform parser and token verification, the per-source hooks, the API client with its python library dependency, the user mapping field and the webhook deployment. The models gain a project. prefix (git.event -> project.git.event, git.branch -> project.git.branch, and so on): generic names like git.commit would claim a global namespace they do not own and could collide with other git integrations installed alongside (e.g. the OCA github_connector suite), while the prefix scopes the models to the project app this connector extends. Every other artifact is renamed accordingly: python files and classes, view and action xmlids, security rules, relation tables, the webhook route (/project_git/webhook/) and the system parameters (project_git.authorization_token, project_github.token, project_gitlab.token.<instance root>). The base module readme documents the contract for writing new platform bridges. The split also hardens the platform API access. The token sysparams are seeded by the bridges with a demo placeholder, and the banned-token guard of the controller now covers the outbound direction too: _get_token_param raises an explicit UserError on a missing or demo token instead of calling the platform with bogus credentials. Task matching runs its searches with a targeted sudo, since webhook jobs execute as the public user, which has no task access of its own. The es_419 translations shipped by the original module are realigned with the new terms: each module carries the entries it owns, keeping the existing translations where the source text survives. Assisted-by: Claude Fable 5
GitHub reports the merge with the "merged" boolean of the payload, never with state "merged": the state mapping entry was unreachable, so a merged PR was tracked as closed and the "MR: Merged" tag was never applied on GitHub tasks. Assisted-by: Claude Fable 5
…queue_job The module only uses with_delay(), which lives in queue_job: queue_job_cron_jobrunner is a deployment choice (a cron-based runner for hostings where the standard jobrunner cannot run, e.g. Odoo.sh) and should not be forced on every installation. project_git now depends on queue_job instead; the install notes mention queue_job_cron_jobrunner as an option for such hostings. Also add a roadmap fragment to project_gitlab, proposing to make the hardcoded odoo_sh_deploy job name of the "Update Odoo.sh" button configurable. Assisted-by: Claude Fable 5
_search_existing_pull_request looked up pull requests by (id_request, id_project) only: a GitLab pair (iid, project id) can collide with a GitHub pair (number, repository id). On collision the event of one platform would overwrite the pull request of the other. The lookup now also filters on the source platform of the event. Each bridge gets a test dispatching an event over an existing pull request with the same identifiers and no source, asserting that a new record is created and the existing one is left untouched. Assisted-by: Claude Fable 5
…name The PR author matching searched res.users by github_username / gitlab_username without limit: two users sharing the same username would raise a singleton error and make the webhook job fail. A Python constraint on res.users now rejects duplicate platform usernames (checked globally, archived users included), and the event search takes the first match as a safeguard for data predating the constraint. No SQL constraint on purpose: res.users is an existing model. Each bridge gets a test asserting that a duplicate username is rejected. Assisted-by: Claude Fable 5
The approved flag was recomputed from the current webhook action, so any event after the approval (e.g. a title edit) reset it to False and removed the "Approved" tag from the linked tasks while the MR was still approved on GitLab. Write the flag only on the approval actions (approved/unapproved): any other action leaves the last known approval state untouched. Assisted-by: Claude Fable 5
The commit/branch/pull request deduplication relies on search-then-create in the event flow: concurrent queue jobs processing the same entity could slip through the search and create duplicates. Add unique SQL constraints on the identifying keys: full_sha (commit), url (branch) and (source, id_project, id_request) (pull request). On a collision the losing job fails and simply finds the existing record when requeued. Assisted-by: Claude Fable 5
Move the pure model tests out of the event flow suites into per-model test files, one per entity as for project and project.task: unique constraints in the base (test_project_git_commit/branch/pull_request), platform username uniqueness and PR identifier collision in the bridges (test_res_users, test_project_git_pull_request, which on gitlab also hosts the _post_message URL fallback test). test_gitlab.py and test_github.py keep the git.event flow tests only. Assisted-by: Claude Fable 5
The source branch URL was always built from project.web_url, which in MR payloads is the target repository: correct when the MR comes from the same repository, wrong for a MR opened from a fork (the source branch lives in the fork). Prefer object_attributes.source.web_url, falling back to project.web_url for push events, as the GitHub bridge already does. The builder is renamed _build_branch_url -> _build_source_branch_url (base and bridges) to state the contract in the name. Assisted-by: Claude Fable 5
ci_status defaulted to "pending", so every pull request immediately tagged its tasks with "CI: Pending" — forever on GitHub, where no pipeline event will ever arrive to update it. Drop the default: the field stays empty until a real pipeline event is tracked, and the CI tag becomes the last known pipeline state. GitHub CI events (check_suite) are noted in the bridge roadmap. Assisted-by: Claude Fable 5
The task tags master-data and logic lived in the base module, modeled on what the GitLab bridge populates: any new connector would need its own vocabulary anyway. The whole tagging process is now per-source: each bridge ships its own tags master-data, manages its own tag namespace and handles the fields it actually populates (full set on GitLab, PR states on GitHub, in platform wording). Base module project_git only dispatches to _assign_tags_to_task_<source> and provides the generic tag replacement helper, skipping records without a source. Assisted-by: Claude Fable 5
- Add a step-by-step configuration overview - Document the queue_job jobrunner requirement: queue_job must be added to the server_wide_modules; on hostings where the jobrunner cannot run (e.g. Odoo.sh), queue_job_cron_jobrunner is advised instead - Document the caveats of explicit id references Assisted-by: Claude Fable 5
FrancescoBallerini
force-pushed
the
17.0-add_project_git_connectors
branch
from
August 15, 2026 05:38
07207b3 to
000b515
Compare
FrancescoBallerini
marked this pull request as ready for review
August 15, 2026 05:57
In the tree view, keep the request id next to the title (author and source branch move right before the related tasks) and make the URL clickable (url widget), as in the branch and commit views. In the form view, show the plain source branch name only when the PR/MR has no tracked branch record (the record link already carries it). Assisted-by: Claude Fable 5
…dispatch naming Before this commit the http route `/project_git/webhook/` dispatched events by type only, so the names of the platform-specific event handlers did not carry the platform. That did not cause many issues directly, but it was a bit misleading and could cause annoying namespace conflicts (for example when implementing a _process_commit_push handler for another platform with different logic from the existing one). This is why, after this commit, the route dispatches on event type + source, invoking _process_<event_type>_<source>: a more standardized API where every bridge explicitly binds each event it handles. For similar reasons the per-source parsers in the controller become _parse_git_request_data_<source>, so the platform-specific hook names stay aligned with the generic method that dispatches them. Along with this alignment, as an additional small cleanup, every dynamically built method name is composed with f-strings. The development readme documents the bridge extension points with code examples. Assisted-by: Claude Fable 5
FrancescoBallerini
force-pushed
the
17.0-add_project_git_connectors
branch
from
August 17, 2026 01:20
989265a to
769d464
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Continuation of #1390
The original work by @alan196, whose commits are preserved as-is in the history, had already introduced several features
taskid#<id>reference in the PR/MR titleodoo_sh_deployCI job on GitLabThis PR introduces the following additional features
support for commit and branch records along with project task tracking. An inbound webhook handles the following:
registers commits, branches and pull/merge requests as Odoo records when matched to a
project.task(explicittaskid#/tid#reference, or a Jira-styleABC-123key found in the task name)keeps the entities correlated with each other, so that each record can be reached from the ones it belongs to. For example, on a pull/merge request event, the request is linked to its source branch and to the commits it carries
the
webhook_gitlabmodule has been revamped and split into a base platform-agnostic connectorproject_gitalong with theproject_gitlabandproject_githubbridges. For reference, seefcee2223d, [REF] webhook_gitlab: split into project_git, project_github, project_gitlabunit test suite covering the three modules
smaller improvements:
Screenshots
Taken on a demo repository with the webhook actually deployed, so the records below were created by the inbound events themselves (not by hand)
📸 1. The Odoo project points to the repository, and deploys the webhook on it
📸 2. A pull request is opened on GitHub, and the connector posts back the link to the matching task
📸 3. The task shows its branch, commits and pull request
📸 4. The pull request record, with its source branch and its commits
📸 5. The same commits and SHAs on the GitHub side
📸 6. Tags follow the pull request state, with no manual step
Why
project.git.*and notgit.*To avoid clashing with more generic implementations such as https://github.com/OCA/interface-git/tree/17.0/github_connector/. Writing a fully generic, project-agnostic connector (or extending the one in
OCA/interface-git) would have meant a lot of extra work on a PR that is already large, so this is a deliberate compromise: a project-scoped connector that keeps its own namespace.Additional Notes for developers and code reviewers
Along with the revamp and split of the modules, I've tried to establish a pattern that could support future bridges (e.g. Bitbucket) in two ways:
I've extended the event dispatching in the webhook controller: it now dispatches to a generic
project.git.eventmodel, whereas previously it dispatched to the pull request model. That model handles the creation of commit, PR and branch records, and the task matching. See (de76608, [REF] webhook_gitlab: refactoring of git.request into git.event)I've added to
project.git.event(and to theproject_gitcodebase in general) a set of hooks resolved throughgetattr(), dispatching platform-specific logic by event source, so that any bridge can implement its own. See (c618bc822, [REF] webhook_gitlab: dispatch platform-specific logic by event source). On top of that, the controller itself now dispatches on event type + source, invoking_process_<event_type>_<source>: each bridge explicitly binds every event it handles with a per-source entrypoint, delegating the shared flows to the sourceless_process_*_eventhelpers of the base (769d464, [REF] project_git, project_github, project_gitlab: standardize event dispatch naming).project_githubandproject_gitlabare fully functional bridges built on this mechanism, so they also serve as reference implementations. Seeproject_git/readme/DEVELOP.mdfor detailsAI assistance
Following the OCA AI policy, the commits carry an
Assisted-by:trailer naming the model that assisted them: