Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for tags #17

Merged
merged 2 commits into from
Oct 23, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ jobs:
- name: Execute tests in the running services
run: |
docker compose run sync

- name: Pytest coverage comment
uses: MishaKav/pytest-coverage-comment@main
with:
16 changes: 11 additions & 5 deletions config.toml.example
Original file line number Diff line number Diff line change
@@ -18,8 +18,14 @@ directory = "../git_hg_sync_repos/"
name = "firefox-releases"
url = "/home/fbessou/dev/MOZI/fake-forge/git/firefox-releases"

[[mappings]]
source.url = "/home/fbessou/dev/MOZI/fake-forge/git/firefox-releases"
source.branch_pattern = "^(esr\\d+)$"
destination.url = "/home/fbessou/dev/MOZI/fake-forge/hg/mozilla-\\1"
destination.branch = "default"
[[branch_mappings]]
source_url = "/home/fbessou/dev/MOZI/fake-forge/git/firefox-releases"
branch_pattern = "^(esr\\d+)$"
destination_url = "/home/fbessou/dev/MOZI/fake-forge/hg/mozilla-\\1"
destination_branch = "default"

[[tag_mappings]]
source_url = "/home/fbessou/dev/MOZI/fake-forge/git/firefox-releases"
tag_pattern = "^FIREFOX_BETA_(\\d+)_(BASE|END)$"
destination_url = "/home/fbessou/dev/MOZI/fake-forge/hg/mozilla-beta"
tags_destination_branch = "tags"
5 changes: 4 additions & 1 deletion git_hg_sync/__main__.py
Original file line number Diff line number Diff line change
@@ -63,7 +63,9 @@ def start_app(
with connection as conn:
logger.info(f"connected to {conn.host}")
worker = PulseWorker(conn, queue, one_shot=one_shot)
app = Application(worker, synchronizers, config.mappings)
app = Application(
worker, synchronizers, [*config.branch_mappings, *config.tag_mappings]
)
app.run()


@@ -73,6 +75,7 @@ def main() -> None:
args = parser.parse_args()
logger = commandline.setup_logging("service", args, {"raw": sys.stdout})
config = Config.from_file(args.config)

sentry_config = config.sentry
if sentry_config and sentry_config.sentry_url:
logger.info(f"sentry url: {sentry_config.sentry_url}")
23 changes: 10 additions & 13 deletions git_hg_sync/application.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import signal
from types import FrameType
from typing import Optional
from typing import Optional, Sequence

from mozlog import get_proxy_logger

from git_hg_sync.events import Push, Tag
from git_hg_sync.mapping import Mapping, MappingMatch
from git_hg_sync.mapping import Mapping, SyncOperation
from git_hg_sync.pulse_worker import PulseWorker
from git_hg_sync.repo_synchronizer import RepoSynchronizer

@@ -18,7 +18,7 @@ def __init__(
self,
worker: PulseWorker,
repo_synchronizers: dict[str, RepoSynchronizer],
mappings: list[Mapping],
mappings: Sequence[Mapping],
):
self._worker = worker
self._worker.event_handler = self._handle_event
@@ -36,20 +36,17 @@ def signal_handler(sig: int, frame: Optional[FrameType]) -> None:
def _handle_push_event(self, push_event: Push) -> None:
logger.info("Handling push event.")
synchronizer = self._repo_synchronizers[push_event.repo_url]
matches_by_destination: dict[str, list[MappingMatch]] = {}
operations_by_destination: dict[str, list[SyncOperation]] = {}

for mapping in self._mappings:
if matches := mapping.match(push_event):
for match in matches:
matches_by_destination.setdefault(match.destination_url, []).append(
match
)

for destination, matches in matches_by_destination.items():
refspecs = []
for match in matches:
refspecs.append((match.source_commit, match.destination_branch))
synchronizer.sync_branches(destination, refspecs)
operations_by_destination.setdefault(
match.destination_url, []
).append(match.operation)

for destination, operations in operations_by_destination.items():
synchronizer.sync(destination, operations)

def _handle_event(self, event: Push | Tag) -> None:
if event.repo_url not in self._repo_synchronizers:
12 changes: 7 additions & 5 deletions git_hg_sync/config.py
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@

import pydantic

from git_hg_sync.mapping import Mapping
from git_hg_sync.mapping import BranchMapping, TagMapping


class PulseConfig(pydantic.BaseModel):
@@ -36,17 +36,19 @@ class Config(pydantic.BaseModel):
sentry: SentryConfig | None = None
clones: ClonesConfig
tracked_repositories: list[TrackedRepository]
mappings: list[Mapping]
branch_mappings: list[BranchMapping]
tag_mappings: list[TagMapping]

@pydantic.model_validator(mode="after")
def verify_all_mappings_reference_tracked_repositories(
self,
) -> Self:

tracked_urls = [tracked_repo.url for tracked_repo in self.tracked_repositories]
for mapping in self.mappings:
if mapping.source.url not in tracked_urls:
for mapping in self.branch_mappings:
if mapping.source_url not in tracked_urls:
raise ValueError(
f"Found mapping for untracked repository: {mapping.source.url}"
f"Found mapping for untracked repository: {mapping.source_url}"
)
return self

3 changes: 3 additions & 0 deletions git_hg_sync/events.py
Original file line number Diff line number Diff line change
@@ -7,6 +7,9 @@ class Push:
branches: dict[
str, str
] # Mapping between branch names (key) and corresponding commit sha (value)
tags: dict[
str, str
] # Mapping between tag names (key) and corresponding commit sha (value)
time: int
pushid: int
user: str
92 changes: 75 additions & 17 deletions git_hg_sync/mapping.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,113 @@
import re
from dataclasses import dataclass
from functools import cached_property
from typing import Sequence, TypeAlias

import pydantic

from git_hg_sync.events import Push


class MappingSource(pydantic.BaseModel):
url: str
branch_pattern: str
@dataclass
class SyncBranchOperation:
# Source (git)
source_commit: str

# Destination (hg)
destination_branch: str


@dataclass
class SyncTagOperation:
# Source (git)
source_commit: str

# Destination (hg)
tag: str
tags_destination_branch: str


class MappingDestination(pydantic.BaseModel):
url: str
branch: str
SyncOperation: TypeAlias = SyncBranchOperation | SyncTagOperation


@dataclass
class MappingMatch:
source_commit: str
destination_url: str
destination_branch: str
operation: SyncBranchOperation | SyncTagOperation


class Mapping(pydantic.BaseModel):
source: MappingSource
destination: MappingDestination
source_url: str

def match(self, event: Push) -> Sequence[MappingMatch]:
raise NotImplementedError()


# Branch Mapping


class BranchMapping(Mapping):
branch_pattern: str
destination_url: str
destination_branch: str

@cached_property
def _branch_pattern(self) -> re.Pattern:
return re.compile(self.source.branch_pattern)
return re.compile(self.branch_pattern)

def match(self, event: Push) -> list[MappingMatch]:
if event.repo_url != self.source.url:
def match(self, event: Push) -> Sequence[MappingMatch]:
if event.repo_url != self.source_url:
return []
matches: list[MappingMatch] = []
for branch_name, commit in event.branches.items():
if not self._branch_pattern.match(branch_name):
continue
destination_url = re.sub(
self._branch_pattern, self.destination.url, branch_name
self._branch_pattern, self.destination_url, branch_name
)
destination_branch = re.sub(
self._branch_pattern, self.destination.branch, branch_name
self._branch_pattern, self.destination_branch, branch_name
)
matches.append(
MappingMatch(
source_commit=commit,
destination_url=destination_url,
destination_branch=destination_branch,
operation=SyncBranchOperation(
source_commit=commit,
destination_branch=destination_branch,
),
)
)
return matches


# Tag Mapping


class TagMapping(Mapping):
tag_pattern: str
destination_url: str
tags_destination_branch: str

@cached_property
def _tag_pattern(self) -> re.Pattern:
return re.compile(self.tag_pattern)

def match(self, event: Push) -> Sequence[MappingMatch]:
if event.repo_url != self.source_url:
return []
matches: list[MappingMatch] = []
for tag_name, commit in event.tags.items():
if not self._tag_pattern.match(tag_name):
continue
destination_url = re.sub(self._tag_pattern, self.destination_url, tag_name)
matches.append(
MappingMatch(
destination_url=destination_url,
operation=SyncTagOperation(
tag=tag_name,
source_commit=commit,
tags_destination_branch=self.tags_destination_branch,
),
)
)
return matches
Loading
Oops, something went wrong.