Skip to content

Update service mapping URLs in preparation for the dist-git migration - #996

Draft
betulependule wants to merge 10 commits into
packit:mainfrom
betulependule:fedora-forge-service-mapping
Draft

Update service mapping URLs in preparation for the dist-git migration#996
betulependule wants to merge 10 commits into
packit:mainfrom
betulependule:fedora-forge-service-mapping

Conversation

@betulependule

@betulependule betulependule commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Service mapping needs to be updated after the migration so that Config.load_authentication() in packit is able to authenticate against Forgejo as dist-git.

  • Ensure smooth transition from Pagure to Forgejo
  • Update tests
  • Create a follow-up issue (to be done after the migration of dist-git): remove all code relevant to the determination of dist-git forge service class (it will no longer be needed and packit would be making unnecessary API calls if the code is not removed)

Fixes #997
Related to #2681

Note: Ignore the name of the branch that this PR was created from. This PR is unrelated to Fedora Forge. I would rename the branch, but don't want to risk breaking the PR / references to the branch.

Merge right before / during the migration of dist-git from Pagure to Forgejo. This mustn't be merged now. I don't think it would break anything if this were merged now, but it would lead to (currently) unnecessary API calls to see whether PagureService is supposed to be used when working with dist-git. I'll keep this PR a draft until then to make sure this doesn't get accidentally merged.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 23, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:38 AM UTC · Completed 11:52 AM UTC
Commit: 2258023 · View workflow run →

@centosinfra-prod-github-app

Copy link
Copy Markdown
Contributor

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [behavioral-contract-change] ogr/factory.pyget_service_class_or_none() was a pure, side-effect-free lookup function. It now makes HTTP network calls for dist-git URLs, introducing latency (up to 5s timeout), mutable cache state, and a new OgrNetworkError exception. While intentional and temporary (marked [XXX]), this fundamentally changes the function's contract for all callers.
    Remediation: Consider making probing opt-in via a parameter, or catch OgrNetworkError internally and fall back to a default.

  • [logic-error] ogr/factory.py:155 — The substring check if dgit_url in parsed_url.hostname causes "src.fedoraproject.org" to match "src.stg.fedoraproject.org". Since DGIT_URLS lists shorter entries first, staging URLs are matched by non-staging entries, causing: (1) the probe hits production instead of staging, (2) the cache is keyed incorrectly, and (3) user overrides via service_mapping_update for staging URLs are bypassed.
    Remediation: Use equality check (dgit_url == parsed_url.hostname) instead of substring containment.

Medium

  • [fail-open] ogr/factory.py:176 — The condition response.status_code != 404 treats any non-404 response (including 5xx server errors) as evidence that Pagure is still running. A transient server error would cause wrong service class selection.
    Remediation: Use response.ok to confirm Pagure is alive; treat non-2xx, non-404 as inconclusive.

  • [error-handling-gap] ogr/factory.py:148 — The probing code only catches ConnectionError and ReadTimeout, but HTTP requests can also raise TooManyRedirects, ChunkedEncodingError, and other RequestException subclasses that propagate unhandled.
    Remediation: Catch requests.exceptions.RequestException as the base class.

  • [new-exception-type] ogr/factory.py:176get_service_class_or_none() now raises OgrNetworkError, which was never part of this function's contract. Functions that call it (get_service_class(), get_project(), get_instances_from_dict()) inherit this new exception path.
    Remediation: Catch network errors internally and fall back gracefully, or coordinate with downstream consumers.

  • [default-parameter-change] ogr/services/pagure/service.py:35PagureService.__init__ default instance_url changed from "https://src.fedoraproject.org" to "https://pagure.io". Any code constructing PagureService() without an explicit instance_url will silently connect to a different server.
    Remediation: Announce in release notes as a breaking change.

  • [scope-creep] ogr/services/pagure/service.py:39 — Changing PagureService's default instance_url is beyond what issue Update SERVICE_MAPPING to support Forgejo dist-git #997 authorizes (which asks to update SERVICE_MAPPING and add a transition mechanism, not change constructor defaults).
    Remediation: Explicitly call out in the PR description as an intentional breaking change.

  • [return-type-change] ogr/factory.py:127get_service_class_or_none() now returns ForgejoService instead of PagureService for dist-git URLs. ForgejoService.__init__ has different parameters than PagureService.__init__ (no read_only, insecure, max_retries, user_agent). Code passing PagureService-specific kwargs will fail.
    Remediation: Verify ForgejoService.__init__ accepts the same kwargs that downstream callers pass for dist-git URLs.

Low

  • [test-adequacy] tests/unit/conftest.py:13 — The autouse fixture only mocks the 404 response (post-migration). No test covers the pre-migration path (non-404 → PagureService) or error paths (ConnectionError/ReadTimeout). The _DGIT_FORGE_CACHE is never cleared between tests.

  • [scope-creep] ogr/services/pagure/project.py:382 — Removing src.fedoraproject.org from is_private() allowlist means during pre-migration (when probing returns PagureService), is_private() will raise OperationNotSupported instead of returning False.

  • [information-disclosure] ogr/factory.py:185OgrNetworkError message includes the user's project URL rather than the actual probe endpoint that failed, making debugging confusing.

  • [race-condition] ogr/factory.py:162_DGIT_FORGE_CACHE uses a non-atomic check-then-act pattern with no thread-safety.

  • [naming-convention] ogr/constant.py:7DGIT_URLS uses an abbreviation not found elsewhere in the codebase. Consider DIST_GIT_URLS.

  • [code-organization] ogr/factory.py:155 — Lazy imports of ForgejoService and PagureService inside the loop body (justified for circular import avoidance but deviates from codebase pattern).

  • [probing-hardcoded-urls] ogr/factory.py:160 — For pkgs.* URLs, the probe hits src.*, assuming atomic migration. Consider documenting this assumption.

  • [cache-module-state] ogr/factory.py:20 — No public API to clear or inspect _DGIT_FORGE_CACHE. Consider exposing a cache-clearing function.

  • [architectural-coherence] ogr/factory.py:155 — Dist-git URLs are registered on both ForgejoService (decorators) and handled by probing code. Static registrations are effectively dead code while probing exists.

  • [api-shape-pattern] ogr/services/pagure/service.py:249 — New timeout parameter has Optional[float] type annotation while existing sibling parameters lack annotations.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

High

  • [logic-error] ogr/factory.py:185 — On the first probe for any dist-git URL (cache miss), the variable now is never assigned. now = time.monotonic() only executes inside the if cache := block, which is skipped when no cache entry exists. _DGIT_FORGE_CACHE[dgit_url] = (now, dgit_service_kls) will raise NameError. Every first call with a dist-git URL will crash.
    Remediation: Move now = time.monotonic() outside the cache-hit block — e.g., immediately before the cache lookup or before the cache-write line.

  • [breaking-api] ogr/services/pagure/service.py:39PagureService.__init__ default instance_url changed from https://src.fedoraproject.org to https://pagure.io. Any downstream caller instantiating PagureService() without an explicit instance_url will silently target a different instance. The integration test had to be patched to compensate.
    Remediation: Coordinate with downstream consumers (packit/packit). Document as a breaking change in release notes.

Medium

  • [breaking-api] ogr/factory.py:189get_service_class_or_none() now raises OgrNetworkError on probe connection/timeout failures. Previously it never raised exceptions. The new behavior is documented in the docstring.
    Remediation: Ensure downstream callers handle OgrNetworkError, or consider returning None as a fallback with a logged warning.

  • [test-inadequate] tests/unit/conftest.py:14 — The autouse mock fixture does not prevent the now NameError. On cold cache, tests will crash before any cached result can be returned.
    Remediation: Fix the NameError first, then add a test that clears _DGIT_FORGE_CACHE to exercise the cold-cache path.

  • [behavioral-change] ogr/factory.py:155get_service_class_or_none() now makes HTTP network calls for dist-git URLs. Previously a pure in-memory lookup. Changes performance and reliability expectations for callers.
    Remediation: Document the network access requirement. Consider opt-out for offline/CI environments.

  • [breaking-api] ogr/services/forgejo/service.py:20_SERVICE_MAPPING now maps dist-git hostnames to ForgejoService instead of PagureService, affecting get_instances_from_dict and direct _SERVICE_MAPPING usage.
    Remediation: Ensure downstream consumers using get_instances_from_dict with dist-git keys are updated.

  • [naming-convention] ogr/constant.py:5DGIT_URLS and _DGIT_FORGE_CACHE use the abbreviation "DGIT" with no precedent in the codebase. Comments consistently use "dist-git".
    Remediation: Rename to DIST_GIT_URLS and _DIST_GIT_FORGE_CACHE.

Low

  • [architectural-conflict] ogr/factory.py:153 — Both @use_for_service decorators on ForgejoService and the probing code resolve dist-git URLs. The probing code takes precedence; the decorators serve as the planned post-migration mechanism (probing code is marked [XXX] for removal).

  • [hostname-confusion] ogr/factory.py:160if dgit_url in parsed_url.hostname uses substring containment rather than exact match. Pre-existing pattern in the _SERVICE_MAPPING lookup.

  • [error-handling-idiom] ogr/factory.py:189 — Error message references the original project URL instead of the probe request_url.

  • [api-shape] ogr/services/pagure/service.py:249timeout parameter added to get_raw_request but not to call_api_raw.

  • [edge-case] ogr/factory.py:189 — HTTP 500 from probe is treated as evidence Pagure is running (status_code != 404).

  • [code-organization] ogr/factory.py:155# [XXX] marker comment; Python convention uses # TODO: or # FIXME:.

  • [naming-convention] tests/unit/conftest.py:14 — Fixture uses imperative setup_ prefix; ends with bare return.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

High

  • [behavioral-contract-change] ogr/factory.py:143get_service_class_or_none() changes from a pure synchronous mapping lookup to a function that makes HTTP requests for dist-git URLs and can now raise OgrNetworkError. Any caller not handling this exception will crash on network failures. This is transitive through get_service_class() and get_project(). While OgrNetworkError is already part of ogr's public API surface (raised from call_api_raw), it was never raised from the service class lookup path before.
    Remediation: Document the behavioral change in release notes. Coordinate with downstream consumers (packit) to add OgrNetworkError handling. Consider returning None on network failure to preserve the or_none semantic contract.

  • [edge-case] ogr/factory.py:181 — When get_project() is called with PagureService custom_instances for dist-git URLs, probing may return ForgejoService. The isinstance(service_inst, kls) check will fail, causing OgrException("Instance of type ForgejoService ... was not provided."). This is a breaking change for existing callers during the transition period.
    Remediation: Document the breaking change, or skip probing when custom_instances contain a service whose hostname matches a dist-git URL.

  • [return-type-semantic-change] ogr/factory.py:143ForgejoService and PagureService have different __init__ signatures. PagureService accepts read_only, insecure, max_retries, user_agent; ForgejoService accepts only instance_url, token, **kwargs. Callers instantiating the returned class with PagureService-specific kwargs will have those parameters silently ignored via **kwargs, resulting in lost configuration (e.g., read_only=True would have no effect).
    Remediation: Ensure all downstream consumers handle ForgejoService constructor signature. Coordinate with Add Forgejo as dist-git support packit#2681.

  • [default-parameter-change] ogr/services/pagure/service.pyPagureService default instance_url changed from https://src.fedoraproject.org to https://pagure.io. Callers relying on the default (e.g., PagureService(token="...")) will silently connect to a different server.
    Remediation: Announce in release notes. Audit downstream callers.

Medium

  • [logic-error] ogr/factory.py:204 — The probing logic checks response.status_code != 404 to decide if Pagure is running. Any non-404 response (including 500, 503 from infrastructure issues) is treated as "Pagure is running." While Forgejo is unlikely to return non-404 on the Pagure-specific /api/0/version path, checking for status_code == 200 with valid JSON would be more robust.
    Remediation: Check for response.status_code == 200 with valid Pagure API version JSON.

  • [error-handling-gap] ogr/factory.py:213 — Only catches ConnectionError and ReadTimeout. Other requests exceptions (TooManyRedirects, SSLError) propagate uncaught. Additionally, PagureService() default constructor creates a session with max_retries=3 and exponential backoff (backoff_factor=30), so a failing probe could block for over a minute before the 5-second timeout kicks in.
    Remediation: Catch requests.exceptions.RequestException. Construct the probe with max_retries=0.

  • [probe-target-mismatch] ogr/factory.py:196pkgs.fedoraproject.org and pkgs.stg.fedoraproject.org URLs always probe src.fedoraproject.org instead of the actual matched host. If migration timing differs between src and pkgs, the wrong service class would be returned silently.
    Remediation: Probe the actual matched host, or document that all 4 hosts migrate atomically.

  • [logic-error] ogr/factory.py:174service_mapping_update override check uses raw string set subtraction against DGIT_URLS. Scheme-prefixed keys (e.g., "https://src.fedoraproject.org") won't match bare hostname entries ("src.fedoraproject.org"), so the override is silently ignored and the probe runs anyway.
    Remediation: Normalize by comparing parsed hostnames.

  • [error-handling-idiom] ogr/factory.pyfrom requests.exceptions import ConnectionError shadows Python's builtin ConnectionError. The existing code in pagure/service.py uses the fully-qualified requests.exceptions.ConnectionError form. While both refer to the same class in modern Python, shadowing builtins is inconsistent with codebase style.
    Remediation: Use the fully-qualified form or import under an alias.

  • [stale-reference] ogr/services/pagure/project.py:826is_private() removes src.fedoraproject.org and src.stg.fedoraproject.org from the known-public hosts list. During the transition period (when the probe returns PagureService), calling is_private() on a dist-git project would raise OperationNotSupported instead of returning False.
    Remediation: Keep the entries until Pagure migration is complete, or make the removal conditional.

Low

  • [scope-creep] ogr/factory.py — The probing logic goes beyond issue Update SERVICE_MAPPING to support Forgejo dist-git #997's explicit TODO items. The issue states migration is all-at-once, which undermines the need for runtime probing. This is a design decision for maintainers.

  • [contradictory-mapping] ogr/services/forgejo/service.py — Dist-git URLs are registered via @use_for_service on ForgejoService AND handled by probing code that may return PagureService. The decorators are effectively dead code while the probing exists, serving as the post-migration fallback (acknowledged by the [XXX] comment).

  • [architectural-coherence] ogr/factory.py — The probing introduces mutable module-level cache state, network I/O, and hardcoded API URLs into a previously declarative lookup module. Marked as temporary ([XXX] remove once the migration of dist-git is finished).

  • [naming-convention] ogr/factory.py — The DGIT abbreviation is not established elsewhere in the codebase. Other constants use full words (CLONE_TIMEOUT, DEFAULT_RO_PREFIX_STRING).

  • [race-condition] ogr/factory.py_DGIT_FORGE_CACHE dict is read/written without synchronization. Benign under CPython's GIL; worst case is redundant probe requests.

  • [code-organization] ogr/factory.pyForgejoService/PagureService deferred imports are inside the for loop body. They could be moved to the function top to avoid re-importing on each iteration (though Python caches module imports).

  • [documentation-comment-format] ogr/services/pagure/service.py — The new timeout parameter on get_raw_request is not documented in the method's docstring Args: section. All other parameters are documented.

  • [intent-alignment] ogr/services/pagure/service.py — The breaking default URL change from src.fedoraproject.org to pagure.io is not explicitly called out as a breaking change in the PR description.

  • [test-integrity] tests/unit/test_factory.py — The pkgs.fedoraproject.org and pkgs.stg.fedoraproject.org URLs do not appear in the test_get_service_class parametrization, so those probing code paths may be untested.


Labels: PR significantly modifies Pagure service implementation (default URL, decorators, probing) alongside Forgejo migration work.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

Review

Findings

High

  • [behavioral-contract-change] ogr/factory.py:127get_service_class_or_none() was a pure, side-effect-free function (dictionary lookup only, no I/O). This PR adds HTTP probing with a 5-second timeout for any dist-git URL, changing the function's behavioral contract: (1) the function now performs network I/O, adding latency; (2) it can now raise OgrNetworkError, which callers were never required to handle; (3) get_service_class() and get_instances_from_dict() also propagate this undocumented exception.
    Remediation: Consider providing a fallback instead of raising on network failure — default to ForgejoService when the probe fails. Update docstrings on get_service_class() and get_instances_from_dict().

  • [logic-error] ogr/factory.py:165 — The probing logic hardcodes probing against src.fedoraproject.org (or src.stg.fedoraproject.org) for ALL dist-git URLs, including pkgs.fedoraproject.org and pkgs.stg.fedoraproject.org. The check if '.stg.' in dgit_url selects staging, but pkgs.fedoraproject.org (no .stg.) probes src.fedoraproject.org. This is only correct if src and pkgs always migrate together simultaneously.
    Remediation: Either document the assumption that all prod dist-git instances migrate atomically, or use per-hostname probe endpoints.

  • [default-parameter-change] ogr/services/pagure/service.py:39PagureService default instance_url changed from 'https://src.fedoraproject.org' to 'https://pagure.io'. Any downstream consumer calling PagureService() without explicit instance_url will silently connect to a different server.
    Remediation: Document this change prominently in release notes. Verify all known consumers pass instance_url explicitly.

Medium

  • [error-handling-gap] ogr/factory.py:175 — The probing logic only catches ConnectionError and ReadTimeout, but the underlying session.request() can also raise other requests.exceptions subclasses (e.g., TooManyRedirects). Any uncaught exception would propagate as an unhandled error.
    Remediation: Broaden the except clause to catch requests.exceptions.RequestException, or at minimum requests.exceptions.Timeout.

  • [logic-error] ogr/factory.py:160 — The probe uses response.status_code != 404 to decide if Pagure is alive. Any non-404 status (including 500, 502, 503 from infrastructure issues) is treated as "Pagure is still alive", causing the wrong service class to be cached for 2 minutes.
    Remediation: Consider treating only 2xx as "Pagure alive". Treat 5xx as inconclusive and skip caching or fall back to ForgejoService.

  • [denial-of-service] ogr/factory.py:168 — The probe creates PagureService() with default retry config (max_retries=3, backoff_factor=30, status_forcelist includes 500/503). Combined with 5s timeout, worst case for a 500/503 response involves retries with 30s/60s/120s backoff, potentially blocking for ~230 seconds.
    Remediation: Instantiate PagureService for probing with max_retries=0 to fail fast, or use plain requests.get() for the probe.

  • [static-mapping-inconsistency] ogr/services/forgejo/service.py:18 — Static @use_for_service decorators register dist-git URLs under ForgejoService in _SERVICE_MAPPING. The probing logic in get_service_class_or_none() runs before the mapping lookup, so during the transition period, the probe result (possibly PagureService) overrides the static mapping.
    Remediation: Document the intentional override relationship. Consider removing @use_for_service decorators from ForgejoService for dist-git URLs during transition and relying solely on the probe.

  • [docstring-contract-mismatch] ogr/factory.py:40 — The use_for_service docstring example still shows @use_for_service("src.fedoraproject.org") on PagureService. The actual code now uses @use_for_service("git.centos.org").
    Remediation: Update the docstring example to reflect current decorator usage.

Low

  • [probe-coupling] ogr/factory.py:168 — Full PagureService instance created solely for probe, coupling the factory module to PagureService constructor. Consider using plain requests.get() instead.

  • [edge-case] ogr/factory.py:151 — Substring check dgit_url in parsed_url.hostname could match unintended hostnames. Consistent with existing codebase pattern but could use exact equality for the probe.

  • [naming-convention] ogr/constant.py:6DGIT_URLS uses abbreviation not established elsewhere. Consider DIST_GIT_URLS.

  • [code-organization] ogr/factory.py:10requests.exceptions imported at top of factory.py, adding a new direct dependency to a module that previously had none.

  • [naming-convention] ogr/factory.pydgit_service_kls uses novel naming; existing pattern is service_kls.

  • [method-signature-inconsistency] ogr/services/pagure/service.py:249timeout parameter added to get_raw_request but not to call_api_raw.

  • [comment-marker] ogr/factory.py:155[XXX] marker not used elsewhere; standard is TODO/FIXME.

  • [test-adequacy] tests/unit/test_factory.py — No test for get_project() with src.fedoraproject.org without custom instances to exercise the probing path.

  • [test-integrity] tests/unit/conftest.py:13autouse=True fixture mocks PagureService.get_raw_request for all unit tests; could silently affect future tests.

  • [error-handling-idiom] ogr/factory.py — Duplicate error-wrapping logic between probe code and PagureService.call_api_raw.

  • [cache-race-condition] ogr/factory.py_DGIT_FORGE_CACHE has no thread synchronization; timestamp stored before probe completes.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (4)

Review

Findings

Critical

  • [breaking-default-change] ogr/services/pagure/service.py — The default instance_url for PagureService.__init__ changed from "https://src.fedoraproject.org" to "https://pagure.io". Any downstream code calling PagureService() without arguments will silently connect to a different server. This is a backward-incompatible change to a public constructor default. Additionally, this default change goes beyond issue Update SERVICE_MAPPING to support Forgejo dist-git #997's explicit scope (which focuses on SERVICE_MAPPING and smooth transition).
    Remediation: Document the default change prominently. Verify that no downstream consumers (especially packit) rely on PagureService() defaulting to src.fedoraproject.org. Consider making instance_url a required parameter.

High

  • [new-exception-contract] ogr/factory.pyget_service_class_or_none() can now raise OgrNetworkError when probing fails for dist-git URLs. Previously this function only returned a class or None and never raised. This is a breaking change to the function's exception contract. The new exception is documented in the docstring, but existing callers may not expect it.
    Remediation: Catch the network error inside get_service_class_or_none and return a fallback (e.g., ForgejoService, since it's registered via decorators), or ensure all callers handle OgrNetworkError.

  • [logic-error] ogr/factory.py — The probing logic hardcodes two API endpoints (src.fedoraproject.org and src.stg.fedoraproject.org) but DGIT_URLS has four entries including pkgs.* variants. The '.stg.' substring check to select the probe endpoint is fragile — pkgs.fedoraproject.org probes src.fedoraproject.org, and pkgs.stg.fedoraproject.org probes src.stg.fedoraproject.org. This assumes all four hosts share the same forge backend, which is not documented or enforced.
    Remediation: Map each DGIT_URL to its corresponding probe endpoint explicitly (e.g., a dict) rather than relying on '.stg.' substring matching.

Medium

  • [network-side-effect-in-lookup] ogr/factory.pyget_service_class_or_none() now performs HTTP requests where it previously did pure in-memory lookup. The 2-minute cache mitigates repeated calls, but the first call (or any call after cache expiry) blocks up to 5s on timeout. This changes the function's performance and reliability characteristics.

  • [service-mapping-conflict] ogr/services/forgejo/service.py — Dist-git URLs are registered via @use_for_service on ForgejoService AND handled by probing logic in get_service_class_or_none. The probing code runs first and takes precedence, making the decorators effectively dead code during the transition period. After migration, both paths agree but the probe still makes unnecessary HTTP calls until the [XXX] code is removed.

  • [fail-open] ogr/factory.py — Probing decides between PagureService and ForgejoService based solely on HTTP 404 vs. any other status. A 500 from a degraded Pagure instance is treated as "Pagure is alive." If the Forgejo replacement responds with 200 to the Pagure API path, PagureService would be incorrectly selected.

  • [test-inadequate] tests/unit/conftest.py_DGIT_FORGE_CACHE not cleared between tests. The module-level cache persists across test runs within the same process. A cached result from one test can prevent the mock from being exercised in later tests, leading to order-dependent test behavior.

  • [test-inadequate] tests/unit/test_factory.py — Tests only validate the post-migration path (autouse mock returns 404 → ForgejoService). No test covers the pre-migration path where Pagure API returns a success response and PagureService should be returned. This means the current production behavior is untested.

  • [exception-handling-gap] ogr/factory.py — The probing code catches only ConnectionError and ReadTimeout. Other requests exceptions (e.g., TooManyRedirects) would propagate as raw exceptions rather than being wrapped in OgrNetworkError. Note: SSLError is a subclass of ConnectionError so it is caught.

Low

  • [edge-case] ogr/factory.py — The service_mapping_update override exclusion uses set difference against bare hostnames (DGIT_URLS). If a caller passes a full URL as a key, the set difference won't match and probing will fire despite the override intent. Impact is limited since the mapping lookup after probing would still apply the override.

  • [naming-convention] ogr/factory.py_DGIT_FORGE_CACHE and _DGIT_FORGE_CACHE_TTL use domain-specific naming where _SERVICE_MAPPING uses domain-neutral naming. The cache is specific to dist-git and is temporary code (marked [XXX]), so the specific naming is defensible.

  • [naming-convention] ogr/constant.pyDGIT abbreviation in DGIT_URLS is not established elsewhere in the codebase. Existing constants use fully spelled-out names. Consider DIST_GIT_URLS or adding an explanatory comment.

  • [variable-naming] ogr/factory.pydgit_service_kls naming differs from service_kls used elsewhere in the same file. The differentiation distinguishes between the probing and mapping resolution paths.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (5)

Review

Findings

High

  • [logic-error] ogr/factory.py:164 — The dist-git URL check uses dgit_url in parsed_url.hostname (substring match) instead of exact equality. Since DGIT_URLS iterates "src.fedoraproject.org" before "src.stg.fedoraproject.org", a staging hostname matches the production entry first ('src.fedoraproject.org' in 'src.stg.fedoraproject.org' is True), causing the probe to hit the production API endpoint instead of staging. The same applies to pkgs.* variants.
    Remediation: Use if dgit_url == parsed_url.hostname: for exact match.

  • [behavioral-contract-break] ogr/factory.py:141get_service_class_or_none() changes from a pure in-memory dict lookup to a function that makes HTTP requests (with 5s timeout) for dist-git URLs and can now raise OgrNetworkError. This is a new failure mode for all callers, including get_service_class() and get_instances_from_dict() which delegate to it.
    Remediation: Consider catching OgrNetworkError internally and falling back to ForgejoService (the post-migration default), preserving the function's no-raise contract for callers.

  • [default-parameter-break] ogr/services/pagure/service.py:35PagureService.__init__ default instance_url changed from "https://src.fedoraproject.org" to "https://pagure.io". Any code constructing PagureService() without specifying instance_url will silently connect to a different server.
    Remediation: Document as a breaking change. The probe code at ogr/factory.py constructs PagureService() with the default — verify this still works correctly for probing.

  • [test-not-updated] tests/unit/test_pagure.py:11 — This test asserts PagureService().hostname == "src.fedoraproject.org", which will fail with the new default instance_url of "https://pagure.io".
    Remediation: Update to assert PagureService().hostname == "pagure.io".

Medium

  • [token-format-contract-break] ogr/services/forgejo/service.py:35 — Token storage changed from f"token {token}" to raw token. If pyforgejo.PyforgejoApi does not add the "token " prefix itself, this breaks Forgejo authentication for all consumers.
    Remediation: Verify that PyforgejoApi handles raw tokens correctly.

  • [service-mapping-break] ogr/services/forgejo/service.py:20 — Both the static @use_for_service decorators and the runtime probing logic can return different service classes for the same dist-git URL. The static mapping always says ForgejoService, but probing may return PagureService. While probing takes precedence, the dual registration creates confusing redundancy.
    Remediation: Document the interaction, or remove the static registration since probing handles the decision dynamically.

  • [probing-hardcoded-urls] ogr/factory.py:172 — Probe logic uses two hardcoded endpoints (src.fedoraproject.org/api/0/version and its staging variant) for all four DGIT_URLS entries. The pkgs.* hostnames are resolved by probing src.*, implicitly assuming all instances migrate in lockstep.
    Remediation: Add a code comment documenting this assumption, or probe the actual hostname.

  • [stale-test-cases] tests/unit/test_factory.py — Old test cases still pass PagureService custom instances for src.stg.fedoraproject.org URLs. Combined with the substring match bug, the probing logic will match these staging URLs against the production src.fedoraproject.org entry, return ForgejoService, and fail the isinstance check on the PagureService custom instance.
    Remediation: Update remaining PagureService-based dist-git test cases, or fix the substring match first.

  • [fail-open] ogr/services/forgejo/service.py:39 — When no token is provided, the api property passes "unused" as the API key to PyforgejoApi rather than omitting authentication entirely.
    Remediation: Check if PyforgejoApi supports None for api_key to cleanly distinguish unauthenticated requests.

  • [docstring-inconsistency] ogr/factory.py:40 — The use_for_service docstring example still shows @use_for_service("src.fedoraproject.org") on PagureService, which is no longer accurate.
    Remediation: Update example to use @use_for_service("git.centos.org").

Low

  • [trust-boundary] ogr/factory.py:178 — Forge detection relies on HTTP response from Fedora infrastructure. Document the trust assumption.
  • [cache-not-invalidated-in-tests] ogr/factory.py — Module-level _DGIT_FORGE_CACHE persists across test cases. The autouse fixture mocks requests but doesn't clear the cache, which could cause order-dependent test behavior.
  • [race-condition] ogr/factory.py_DGIT_FORGE_CACHE read/write is not synchronized for multi-threaded use. Worst case is redundant probe requests.
  • [naming-convention] ogr/constant.py:6DGIT abbreviation is non-standard in the codebase. Consider DISTGIT_URLS for clarity.
  • [scope-creep] ogr/services/forgejo/service.py — Token handling change and __str__/__eq__/__hash__ additions go beyond issue Update SERVICE_MAPPING to support Forgejo dist-git #997's stated scope. Likely prerequisites, but should be noted in the PR description.
  • [error-message-consistency] ogr/services/forgejo/service.py — Error wording "Invalid Forgejo URL" differs from PagureService's "Cannot parse project url" pattern.

Labels: PR modifies Pagure service mapping and adds Forgejo dist-git support


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (6)

Review

Findings

Critical

  • [behavioral-contract-change] ogr/factory.pyget_service_class_or_none() now raises OgrNetworkError for dist-git URLs when the Pagure probe request fails with ConnectionError or ReadTimeout. Previously this function returned Optional[type[GitService]] and never raised exceptions. Downstream callers (get_service_class, get_instances_from_dict, get_project, and packit's Config.load_authentication()) do not handle this new exception type. A transient network failure during dist-git resolution will crash these callers rather than degrade gracefully.
    Remediation: Either catch the error inside get_service_class_or_none and return a fallback (e.g., ForgejoService from the static mapping) or None, or update all callers — including get_instances_from_dict (which also calls get_service_class_or_none without a try/except) — to handle OgrNetworkError.

High

  • [stale-test] tests/unit/test_pagure.py:11 — The test asserts PagureService().hostname == "src.fedoraproject.org", but this PR changes the default instance_url from "https://src.fedoraproject.org" to "https://pagure.io". This test will fail.
    Remediation: Update the assertion to expect "pagure.io".

  • [default-parameter-change] ogr/services/pagure/service.pyPagureService.__init__() default instance_url changed from "https://src.fedoraproject.org" to "https://pagure.io". This is a backward-incompatible change for any downstream code constructing PagureService() without an explicit instance_url.
    Remediation: Audit downstream callers (packit and other ecosystem tools). Document the breaking change in the changelog.

  • [token-format-change] ogr/services/forgejo/service.py:35 — Token storage changed from self._token = f"token {token}" to self._token = token, removing the "token " prefix. The token is passed directly to PyforgejoApi(api_key=self._token). This changes authentication behavior for all ForgejoService users, not just dist-git.
    Remediation: Verify that PyforgejoApi handles raw tokens correctly (i.e., adds the prefix internally). Document the change.

Medium

  • [service-type-change] ogr/factory.py — Fedora dist-git URLs now map to ForgejoService instead of PagureService (both via static @use_for_service decorators and the runtime probe). Callers passing custom_instances containing PagureService instances for these hostnames will fail type matching in get_project().

  • [scope-coherence] ogr/factory.py — Dual mechanisms exist for the same URLs: static @use_for_service decorators register ForgejoService, while the runtime probe can return PagureService. The probe intercepts before the static mapping is consulted, so the decorators have no effect during the transition period. On unexpected exceptions (anything other than ConnectionError/ReadTimeout), the error propagates unhandled rather than falling through to the static mapping.

  • [probe-logic-asymmetry] ogr/factory.pypkgs.fedoraproject.org and pkgs.stg.fedoraproject.org are probed via src.fedoraproject.org and src.stg.fedoraproject.org respectively. This assumes all dist-git hostnames share the same backend, which is undocumented.

  • [unauthenticated-probing-mismatch] ogr/factory.py — The probe creates PagureService() with the new default instance_url="https://pagure.io" but makes the probe request to src.fedoraproject.org. The service instance's session and headers are configured for pagure.io, not the probe target.

  • [missing-test-coverage] tests/unit/conftest.py:14 — The autouse fixture always returns a 404 response (post-migration state). No test verifies the pre-migration path where the Pagure API returns a non-404 status and PagureService should be returned.

  • [authentication] ogr/services/forgejo/service.py:35 — When token=None, the old code produced self._token = "token None" (a truthy string); the new code produces self._token = None (falsy). Verify that PyforgejoApi(api_key=None) correctly handles unauthenticated mode.

Low

  • [scope-alignment] ogr/services/forgejo/service.py — The token prefix removal and get_project_from_url refactoring are unrelated to the dist-git migration. Bundling unrelated changes into a time-sensitive PR increases merge risk.
  • [error-message-consistency] ogr/services/forgejo/service.py — Error message "Invalid Forgejo URL" differs from the base class pattern "Cannot parse project url". The override now duplicates BaseGitService.get_project_from_url and could be removed.
  • [naming-convention] ogr/factory.py — The dgit abbreviation (DGIT_URLS, _DGIT_FORGE_CACHE, etc.) is not used elsewhere in the codebase. Consider DIST_GIT_URLS for clarity.
  • [cache-issues] ogr/factory.py — The module-level _DGIT_FORGE_CACHE has a 2-minute TTL but no manual invalidation mechanism for long-running processes.
  • [stale-references] tests/unit/test_factory.py — Verify that all test parametrization for test_get_instances_from_dict is updated consistently with the docstring changes.
  • [error-message-misleading] ogr/factory.py — The OgrNetworkError message says "Cannot connect to url: '{url}'" where url is the original project URL, not the probe endpoint URL that actually failed.
  • [comment-marker] ogr/factory.py[XXX] comment marker is not used elsewhere in the codebase; the convention is TODO:.
  • [docstring-format] ogr/factory.py — The Raises: section uses "in case" phrasing; the codebase convention uses "if".

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (7)

Review

Findings

High

  • [breaking-return-type-change] ogr/factory.py — The return type of get_service_class_or_none(), get_service_class(), and get_project() changes from PagureService/PagureProject to ForgejoService/ForgejoProject for all dist-git URLs. Downstream consumers that rely on the result being a PagureService or PagureProject instance (e.g., isinstance() checks, Pagure-specific methods) will break. While this is the deliberate intent of the PR, downstream repos (packit, bodhi, etc.) must be audited and coordinated before this lands.
    Remediation: Audit downstream consumers for dist-git URL assumptions. Coordinate the ogr release with downstream updates. Add a changelog entry or migration guide noting the type change.

  • [new-exception-from-existing-api] ogr/factory.pyget_service_class_or_none() (and transitively get_service_class() and get_project()) now makes a network call to the Pagure API and raises OgrNetworkError on ConnectionError. Previously these functions were pure lookups that never performed I/O. Downstream callers that do not catch OgrNetworkError will see unhandled exceptions in offline or network-degraded environments. This also introduces up to 5 seconds of latency per uncached call.
    Remediation: Document the new exception in the docstrings. Consider extracting the probing logic into a clearly named helper and whether a fallback should be returned instead of raising when the network is unavailable.

Medium

  • [error-handling-gap] ogr/factory.py — The except clause only catches requests.exceptions.ConnectionError, but the timeout=5 parameter can cause requests.exceptions.ReadTimeout (a subclass of Timeout, NOT of ConnectionError). A ReadTimeout would propagate as an unhandled exception instead of being wrapped in OgrNetworkError.
    Remediation: Broaden the except clause to catch (requests.exceptions.ConnectionError, requests.exceptions.Timeout) or requests.exceptions.RequestException.

  • [logic-error] ogr/services/forgejo/service.pyForgejoService.__str__ is missing a closing parenthesis in the returned string. It returns ForgejoService(instance_url='...' without a trailing ). Compare with PagureService.__str__ which correctly ends with ). Since __hash__ delegates to __str__, this also produces malformed hash inputs.
    Remediation: Add a closing ) to the return f-string.

  • [logic-error] ogr/services/forgejo/service.py — The __str__ method's guard if self._token else "" is dead code. In __init__, self._token is unconditionally set to f"token {token}", which produces "token None" when token=None. This is always truthy, so the guard never triggers. When no token is provided, __str__ will display token='t***e' (the first and last characters of "token None"), which is misleading.
    Remediation: Guard on the raw token parameter value rather than self._token, or align the storage pattern with PagureService (which stores self._token = token directly).

  • [null-dereference] ogr/factory.pyparse_git_repo(url) can return None for malformed URLs, but the result parsed_url is used without a null check: if dgit_url in parsed_url.hostname would raise AttributeError: 'NoneType' object has no attribute 'hostname'.
    Remediation: Add a null check after parse_git_repo(url) and return None early if the URL cannot be parsed.

  • [intent-contradiction] ogr/services/forgejo/service.py — The PR simultaneously registers dist-git hostnames to ForgejoService via @use_for_service decorators AND adds runtime probing logic in get_service_class_or_none that intercepts these URLs before the mapping is consulted. These two mechanisms work against each other: the decorators populate _SERVICE_MAPPING with ForgejoService, but the runtime probe runs first and can return PagureService instead. If the probe fails with a non-ConnectionError exception, it falls through to the mapping which returns ForgejoService.
    Remediation: Choose one mechanism: either @use_for_service decorators (merge only when migration is complete) or runtime probing (remove dist-git decorators from ForgejoService).

  • [service-mapping-update-override-mismatch] ogr/factory.pyDGIT_URLS entries are bare hostnames (e.g., "src.fedoraproject.org") while service_mapping_update keys can be full URLs (e.g., "https://src.fedoraproject.org"). The set subtraction set(non_overrided_dgit_urls) - service_mapping_update.keys() will never match URL-format keys, so the override mechanism is broken: callers cannot prevent the runtime detection from overriding their custom mapping.
    Remediation: Normalize the comparison — either parse both to extract hostnames, or document that service_mapping_update keys must use bare hostnames to override dist-git detection.

  • [naming-convention] ogr/factory.py — The variable non_overrided_dgit_urls uses incorrect English past participle. The correct form is "overridden", not "overrided".
    Remediation: Rename to non_overridden_dgit_urls.

  • [stale-reference] CONTRIBUTING.md — The "Documentation of the services' APIs" section lists src.fedoraproject.org under "Pagure (through requests)". This PR moves the dist-git URL mapping to ForgejoService. There is also no Forgejo entry in the API docs list at all.
    Remediation: Add a "Forgejo (through pyforgejo)" entry. Update the src.fedoraproject.org reference once migration completes.

Low

  • [scope-creep] ogr/factory.py — The runtime API-probing mechanism is a novel pattern not found elsewhere in ogr. The PR body acknowledges this as the approach for a smooth transition. Worth discussing the design in issue Update SERVICE_MAPPING to support Forgejo dist-git #997 or a dedicated issue.

  • [runtime-detection-incorrect-for-pkgs-urls] ogr/factory.py — Runtime detection probes src.fedoraproject.org / src.stg.fedoraproject.org but never probes pkgs.* endpoints directly. Assumes pkgs.* and src.* always share the same forge backend.

  • [test-isolation] tests/unit/conftest.py_DGIT_FORGE_CACHE is module-level state that persists across tests and is never cleared. The first test that triggers dist-git detection populates the cache; subsequent tests within the TTL use cached values rather than the mock.

  • [hash-equality-contract] ogr/services/forgejo/service.py__hash__ is based on __str__ (masked token) while __eq__ compares the full self._token. Tokens with the same first/last character but different middle will collide in hash but not in equality.

  • [backward-compatible-signature-extension] ogr/services/pagure/service.pyget_raw_request() gains a new timeout parameter with default None. Backward-compatible for callers, but subclasses overriding it without the new parameter may break.

  • [blank-line-style] ogr/services/forgejo/service.py — Extra blank line between token_str assignment and return in __str__. PagureService and GitlabService __str__ have no such blank line.

  • [blank-line-style] ogr/factory.py — Excessive blank lines within the dist-git detection loop body, inconsistent with the rest of the codebase.

  • [missing-documentation] CHANGELOG.md — No changelog entry for the behavioral changes introduced by this PR.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (8)

Review

Findings

Medium

  • [stale-default-value] ogr/services/pagure/service.py:39PagureService.__init__ still defaults instance_url to "https://src.fedoraproject.org", but this PR remaps that domain to ForgejoService. A bare PagureService() instantiation would create a Pagure client pointed at what will be a Forgejo instance after migration, causing silent misbehavior.
    Remediation: Change the default instance_url to a domain that remains mapped to PagureService (e.g., "https://pagure.io").

  • [authentication/credential-routing] ogr/services/forgejo/service.py:20 — Remapping the four Fedora dist-git hostnames changes how authentication tokens are formatted and which API client receives them (PyforgejoApi vs. raw requests). The PR is correctly marked as draft with an explicit merge-timing warning, which provides adequate procedural protection.
    Remediation: Ensure this PR is only merged after the dist-git migration is confirmed complete.

Low

  • [stale-reference] tests/unit/test_factory.py:38 — Multiple test cases assert src.fedoraproject.org URLs resolve to PagureService. These will fail after the mapping change. The PR description acknowledges "TODO - Update tests."

  • [stale-reference] ogr/services/pagure/project.py:385PagureProject.is_private() hardcodes src.fedoraproject.org and src.stg.fedoraproject.org as Pagure instances. After remapping, these entries become dead code.

  • [stale-documentation] ogr/factory.py:32 — The use_for_service docstring example shows src.fedoraproject.org mapped to PagureService, which will be incorrect after this PR.

  • [stale-docstring] ogr/factory.py:183 — The get_instances_from_dict docstring example uses src.fedoraproject.org as a PagureService instance URL. While the dict key resolution still works correctly, the URL is misleading.

  • [stale-documentation] CONTRIBUTING.md:28src.fedoraproject.org is listed under the Pagure section as a Pagure API endpoint.

  • [authentication/token-handling] ogr/services/forgejo/service.py:31 — Pre-existing: ForgejoService formats token=None as "token None". Expanded scope increases surface area.

  • [scope-completeness] — Test updates (noted as TODO) should be completed before the PR exits draft.

Previous run (9)

Review

Findings

High

  • [overly broad service matching] ogr/services/forgejo/service.py:18 — The @use_for_service("forge") decorator registers "forge" in the service mapping. The matching logic in factory.py uses substring matching (parse_git_repo(service).hostname in parsed_url.hostname), so any URL whose hostname contains "forge" will match ForgejoService — including unrelated services like sourceforge.net.
    Remediation: Use a more specific service identifier (e.g., the actual Fedora Forge hostname or "fedora-forge") that won't substring-match unintended hostnames. See also: [naming-convention] finding at this location.

Medium

  • [default instance_url change breaks existing callers] ogr/services/forgejo/service.py:30 — The default instance_url was changed from "https://codeberg.org" to "https://src.fedoraproject.org". Any existing code that constructs ForgejoService() without explicitly passing instance_url (expecting Codeberg) will now silently connect to src.fedoraproject.org. See also: [default-value-consistency] finding at this location.
    Remediation: Keep the default as "https://codeberg.org" and require Fedora Forge users to pass the instance URL explicitly.

  • [naming-convention] ogr/services/forgejo/service.py:18 — The @use_for_service("forge") decorator breaks the established naming pattern. Every other decorator uses the forge software name ("github.com", "gitlab", "pagure", "forgejo") or a specific hostname ("codeberg.org", "src.fedoraproject.org"). "forge" is neither. See also: [overly broad service matching] finding at this location.
    Remediation: Remove @use_for_service("forge") unless there is a concrete instance hostname that requires it.

  • [default-value-consistency] ogr/services/forgejo/service.py:30 — Changing the default instance_url is a breaking change to the ForgejoService constructor's public API. Other service classes keep their defaults stable (e.g., PagureService defaults to "https://src.fedoraproject.org", GithubService defaults to "https://github.com"). See also: [default instance_url change breaks existing callers] finding at this location.
    Remediation: Keep the default as instance_url: str = "https://codeberg.org".

Low

  • [stale reference in docstring] ogr/factory.py:32 — The docstring example for use_for_service shows @use_for_service("src.fedoraproject.org") under PagureService, but this PR moves that registration to ForgejoService. The example becomes misleading.
    Remediation: Update the docstring example to reflect the current mapping.

Labels: PR modifies Forgejo service mapping and adds Fedora dist-git domain registrations

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added area/forgejo Forgejo-forge related area/fedora Related to Fedora ecosystem labels Jul 23, 2026
@betulependule
betulependule force-pushed the fedora-forge-service-mapping branch from 2258023 to 3bf6904 Compare July 23, 2026 11:58
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 23, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 11:59 AM UTC · Ended 12:14 PM UTC
Commit: 3bf6904 · View workflow run →

@centosinfra-prod-github-app

Copy link
Copy Markdown
Contributor

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review July 23, 2026 12:14

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 23, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:59 AM UTC · Completed 12:14 PM UTC
Commit: 3bf6904 · View workflow run →

@lbarcziova

Copy link
Copy Markdown
Member

Merge after the migration of dist-git from Pagure to Forgejo. This mustn't be merged now, otherwise it will break authentication against Pagure. I'll keep this PR a draft until then to make sure this doesn't get accidentally merged.

could we make it work with both at the same time? I assume that would ease up testing and transition period.

@mfocko

mfocko commented Jul 24, 2026

Copy link
Copy Markdown
Member

could we make it work with both at the same time? I assume that would ease up testing and transition period.

I don’t think both should be a problem, though… I we cannot have both Forgejo and Pagure under one domain, and also I’m not sure how would the syncing on Fedora Infra work… afaik the changes flow only from prod to stage on Fedora Infra, not the other way around… yet… we would submit production builds and changes, so… this would be a mess

@betulependule

Copy link
Copy Markdown
Contributor Author

Merge after the migration of dist-git from Pagure to Forgejo. This mustn't be merged now, otherwise it will break authentication against Pagure. I'll keep this PR a draft until then to make sure this doesn't get accidentally merged.

could we make it work with both at the same time? I assume that would ease up testing and transition period.

I think it should be possible to support both? Packit uses the get_service_class_or_none to get the appropriate service class, which would (in the current implementation) only return an instance of PagureService or ForgejoService (whichever it got to first in the for loop). This function could be modified so that ogr checks which one should be used, or we could use a new function, which would return all candidate service classes and packit would handle the checks.

If the migration of all repos happens at once, we can check the backend at runtime: GET src.fedoraproject.org/api/0/version (Pagure) and GET src.fedoraproject.org/api/v1/version (Forgejo) to see, which returns OK 200.

Or if the migration is gradual per-repo, we would need GET src.fedoraproject.org/api/v1/rpms/<package> to see whether the Forgejo backend is in use for the given repo at runtime. I'm not sure about the amount of API calls this would lead to. We would possibly need caching.

@betulependule

betulependule commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

I asked the team behind the migration and I was told they plan for the migration of all repos to happen all at once. They will migrate all repos to a new deployment and once that is done, make src.fp.o point to the new deployment.

It would still be problematic for us if we simply edit SERVICE_MAPPING during the migration, because we would then need to wait for the new release of ogr for Packit to use the updated mapping and actually work for Forgejo dist-git.

I suppose we could:

  1. Right before the migration, release ogr with the following change: The get_service_class_or_none() and get_service_class() methods make API calls to check whether dist-git is currently hosted on Pagure or Forgejo, and return the appropriate class when needed (PagureService or ForgejoService).
  2. Once the migration is done, we release ogr once again, remove the API calls, and put in a static SERVICE_MAPPING of src.fedoraproject.org to ForgejoService.

I can't say I like this solution and it would be very clunky with a lot of unnecessary API calls, so I hope there could be a better solution.

@betulependule
betulependule force-pushed the fedora-forge-service-mapping branch from 3bf6904 to 3295e11 Compare August 5, 2026 08:01
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 5, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 8:02 AM UTC · Ended 8:30 AM UTC
Commit: 3295e11 · View workflow run →

@betulependule
betulependule force-pushed the fedora-forge-service-mapping branch from 3295e11 to 98d1358 Compare August 5, 2026 08:30
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 5, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 8:31 AM UTC · Ended 8:51 AM UTC
Commit: 98d1358 · View workflow run →

@centosinfra-prod-github-app

Copy link
Copy Markdown
Contributor

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Aug 5, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:31 AM UTC · Completed 8:51 AM UTC
Commit: 98d1358 · View workflow run →

@betulependule

Copy link
Copy Markdown
Contributor Author

If my understanding is correct, the requre tests are failing because of the API call to https://src.fedoraproject.org/api/0/version (a lacking recording of a response to this GET request). Multiple files in the packit repo (in packit/tests_recording/test_data/test_status/) need to have this response added in order for tests to pass in this PR.

@betulependule
betulependule force-pushed the fedora-forge-service-mapping branch from 98d1358 to 440e8c1 Compare August 5, 2026 11:29
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 5, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 11:30 AM UTC · Ended 12:10 PM UTC
Commit: 440e8c1 · View workflow run →

@centosinfra-prod-github-app

Copy link
Copy Markdown
Contributor

@betulependule
betulependule force-pushed the fedora-forge-service-mapping branch from 95ef178 to 0d6cbd8 Compare August 7, 2026 07:08
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 7, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 7:09 AM UTC · Ended 7:29 AM UTC
Commit: 0d6cbd8 · View workflow run →

@centosinfra-prod-github-app

Copy link
Copy Markdown
Contributor

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:09 AM UTC · Completed 7:28 AM UTC
Commit: 0d6cbd8 · View workflow run →

"https://src.fedoraproject.org" will no longer be associated with
Pagure after the migration. The value of `instance_url` has been
changed to "https://pagure.io".
The two hostnames will no longer be associated with Pagure after
the migration of dist-git and should be removed. The edit to the
test recording is more of a hot fix. It would be probably better
to add a new recording, but not sure if it's worth it.
@betulependule
betulependule force-pushed the fedora-forge-service-mapping branch from 0d6cbd8 to 6a76adb Compare August 7, 2026 07:52
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 7, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 7:53 AM UTC · Ended 8:31 AM UTC
Commit: 6a76adb · View workflow run →

@centosinfra-prod-github-app

Copy link
Copy Markdown
Contributor

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the pagure Related to Pagure implementation. label Aug 7, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:53 AM UTC · Completed 8:31 AM UTC
Commit: 6a76adb · View workflow run →

Comment thread ogr/factory.py

parsed_url = parse_git_repo(url)

# [XXX] remove once the migration of dist-git is finished

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an FYI:

  • you need to keep in mind that this will probably get also released, so even if we remove it after the migration is finished, it might live somewhere for some time…
  • also, it would be ideal to have this in some release before the migration

Comment thread ogr/factory.py
Comment on lines +166 to +167
api_endpoint = "https://src.fedoraproject.org/api/0/version"
api_endpoint_stg = "https://src.stg.fedoraproject.org/api/0/version"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to say that it doesn’t work for pagure.io, so it’s some dist-git specific patch, but… now that I checked the forge itself, I cannot seem to get there altogether…

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure? I can get to "https://src.stg.fedoraproject.org/api/0/version" just fine.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pagure.io, not staging dist-git

Comment thread ogr/factory.py Outdated
response = pagure_service.get_raw_request(url=request_url)

# if not found, then dist-git is no longer hosted on Pagure
if response.status_code != 404:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#sarcastic 50x could also mean it’s a Pagure…

Comment thread ogr/factory.py Outdated
self,
token: Optional[str] = None,
instance_url: str = "https://src.fedoraproject.org",
instance_url: str = "https://pagure.io",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is going to die too tbf, not sure if before or after dist-git

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, as you pointed out, "pagure.io" is read-only now. I could change instance_url to "git.centos.org", but if that's going to die as well, then I might as well keep it set to "src.fedoraproject.org".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(best would be no default, but… that is breaking change… idk, the pagure.io is probably the most sane default)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, changing instance_url from "https://src.fedoraproject.org" to anything else is a breaking change regardless. I'm thinking that once this is released before the migration, whoever is using PagureService without explicitly setting instance_url to "https://src.fedoraproject.org" will run into an error, despite dist-git still being on Pagure at that point. Maybe it's safer to keep the default value set to "https://src.fedoraproject.org" and only change it in a follow-up PR after the migration to make sure no users are affected before the migration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As fullsend pointed out, another breaking change similar to this one is in pagure/project.py‎ where removing the hardcoded dist-git URLs would break the is_private method whenever called pre-migration.

I think it would be best to drop 0506e3d7c9a45c16a2f9ef2fcce5097bd5b2bf1e and d044e7c6932f22179a4e2bf08ef599ac1f7d0276 from this PR, then create a follow-up PR with these two commits and merge it after migration, not before.

Comment thread tests/unit/test_pagure.py
Co-authored-by: Matej Focko <mfocko@users.noreply.github.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 10, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 2:26 PM UTC · Ended 2:48 PM UTC

Commit: ef885fc · View workflow run →

@centosinfra-prod-github-app

Copy link
Copy Markdown
Contributor

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:26 PM UTC · Completed 2:48 PM UTC

Commit: ef885fc · View workflow run →

mfocko added a commit to mfocko/packit that referenced this pull request Aug 10, 2026
Related to packit/ogr#996

Signed-off-by: Matej Focko <mfocko@packit.dev>
Comment thread ogr/factory.py
centosinfra-prod-github-app Bot added a commit that referenced this pull request Aug 11, 2026
Fix `get_project_from_url` repo parsing in `ForgejoService`

The duplication of two existing test cases and editting them to test code in relation to ForgejoService revealed that the parsing of  repo from a given url was incomplete. The get_project_from_url method would extract python-dockerpty.git instead of the expected string python-dockerpty. That is because the stripping of the .git extension was omitted in the previous implementation. It is now fixed.
This bug was discovered in: #996.
I've separated the fix to this separate PR as it's not directly related to #996.
RELEASE NOTES BEGIN
The ForgejoService.get_project_from_url() method has been updated to ensure repository names are parsed accurately. It now correctly removes the .git extension from the repository name, addressing an issue where the extension was previously retained.
RELEASE NOTES END

Reviewed-by: fullsend-ai-review[bot]
Reviewed-by: Alžběta Kučerová
Reviewed-by: Tomas Tomecek <tomas@tomecek.net>
Reviewed-by: Matej Focko
Co-authored-by: Matej Focko <mfocko@users.noreply.github.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 11, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 9:26 AM UTC · Ended 10:06 AM UTC

Commit: 3e3a496 · View workflow run →

@centosinfra-prod-github-app

Copy link
Copy Markdown
Contributor

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.


Note: The following inline comments could not be posted on the diff (GitHub returned 422) and are included here instead:

  • ogr/factory.py:127: [high] behavioral-contract-change

get_service_class_or_none() was a pure, side-effect-free lookup function. It now makes HTTP network calls for dist-git URLs, introducing latency (up to 5s timeout), mutable cache state, and a new OgrNetworkError exception. While intentional and temporary (marked [XXX]), this fundamentally changes the function's contract for all callers.

Suggested fix: Consider making probing opt-in via a parameter (e.g. probe_dist_git=True), or catch OgrNetworkError internally and fall back to a default.

  • ogr/factory.py:155: [high] logic-error

The substring check if dgit_url in parsed_url.hostname causes 'src.fedoraproject.org' to match 'src.stg.fedoraproject.org' (and pkgs vs pkgs.stg). Since DGIT_URLS lists shorter entries first, staging URLs are matched by non-staging entries, causing: (1) the probe hits production instead of staging, (2) the cache is keyed incorrectly, and (3) user overrides via service_mapping_update for staging URLs are bypassed.

Suggested fix: Use equality check (dgit_url == parsed_url.hostname) instead of substring containment.

  • ogr/factory.py:176: [medium] fail-open

The condition response.status_code != 404 treats any non-404 response (including 500, 502, 503) as evidence that Pagure is still running. A transient server error would cause wrong service class selection.

Suggested fix: Use response.ok to confirm Pagure is alive; treat non-2xx, non-404 responses as inconclusive or default to ForgejoService.

  • ogr/factory.py:148: [medium] error-handling-gap

The probing code only catches ConnectionError and ReadTimeout, but HTTP requests can also raise TooManyRedirects, ChunkedEncodingError, and other RequestException subclasses that propagate unhandled.

Suggested fix: Catch requests.exceptions.RequestException as the base class instead.

  • ogr/factory.py:176: [medium] new-exception-type

get_service_class_or_none() now raises OgrNetworkError, which was never part of this function's contract. Functions that call it (get_service_class(), get_project(), get_instances_from_dict()) inherit this new exception path. Downstream consumers are unlikely to handle it.

Suggested fix: Catch network errors internally and fall back gracefully, or coordinate with downstream consumers to add error handling.

  • ogr/services/pagure/service.py:35: [medium] default-parameter-change

PagureService.init default instance_url changed from 'https://src.fedoraproject.org' to 'https://pagure.io'. Any code constructing PagureService() without an explicit instance_url will silently connect to a different server.

Suggested fix: Announce in release notes as a breaking change.

  • ogr/services/pagure/service.py (file-level): Line 39 · [medium] scope-creep

Changing PagureService's default instance_url is beyond what issue #997 authorizes (which asks to update SERVICE_MAPPING and add a transition mechanism, not change constructor defaults).

Suggested fix: Explicitly call out in the PR description as an intentional breaking change and confirm with maintainers.

  • ogr/factory.py:127: [medium] return-type-change

get_service_class_or_none() now returns ForgejoService instead of PagureService for dist-git URLs. ForgejoService.init has different parameters than PagureService.init (no read_only, insecure, max_retries, user_agent). Code passing PagureService-specific kwargs will fail.

Suggested fix: Verify ForgejoService.init accepts the same kwargs that downstream callers pass for dist-git URLs. Document the return type change.

  • tests/unit/conftest.py:13: [low] test-adequacy

The autouse fixture only mocks the 404 response (post-migration). No test covers the pre-migration path (non-404 returning PagureService) or error paths (ConnectionError/ReadTimeout raising OgrNetworkError). The _DGIT_FORGE_CACHE is never cleared between tests.

  • ogr/services/pagure/project.py:382: [low] scope-creep

Removing src.fedoraproject.org and src.stg.fedoraproject.org from is_private() allowlist means during pre-migration (when probing returns PagureService), is_private() will raise OperationNotSupported instead of returning False.

  • ogr/factory.py:185: [low] information-disclosure

OgrNetworkError message includes the user's project URL (url parameter) rather than the actual probe endpoint that failed (request_url), making debugging confusing.

  • ogr/factory.py:162: [low] race-condition

_DGIT_FORGE_CACHE uses a non-atomic check-then-act pattern with no thread-safety controls. Worst case: redundant probe requests in multi-threaded environments.

  • ogr/constant.py:7: [low] naming-convention

DGIT_URLS uses an abbreviation ('DGIT') not found elsewhere in the codebase. Existing constants use full descriptive names (CLONE_TIMEOUT, DEFAULT_RO_PREFIX_STRING).

  • ogr/factory.py:155: [low] code-organization

Lazy imports of ForgejoService and PagureService inside the for loop body. While justified to avoid circular imports, this deviates from the codebase's top-level import pattern.

  • ogr/factory.py:160: [low] probing-hardcoded-urls

For pkgs.fedoraproject.org URLs, the probe hits src.fedoraproject.org, assuming pkgs and src domains migrate atomically. This assumption is undocumented.

  • ogr/factory.py:20: [low] cache-module-state

No public API to clear or inspect _DGIT_FORGE_CACHE. In long-running processes, cache could serve stale results for up to 2 minutes after migration.

  • ogr/factory.py:155: [low] architectural-coherence

Dist-git URLs are registered on both ForgejoService (via @use_for_service decorators) and handled by probing code. The static registrations are effectively dead code while probing exists, but serve as the post-migration fallback when probing is removed.

  • ogr/services/pagure/service.py:249: [low] api-shape-pattern

New timeout parameter has Optional[float] type annotation while existing sibling parameters (params, data, header) in the same method lack type annotations.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:26 AM UTC · Completed 10:06 AM UTC

Commit: 3e3a496 · View workflow run →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/fedora Related to Fedora ecosystem area/forgejo Forgejo-forge related kind/feature A request, idea, or new functionality pagure Related to Pagure implementation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update SERVICE_MAPPING to support Forgejo dist-git

3 participants