Repository: packit-service/packit
Pull request #76 (2019-02-28 10:08:22)

API for bots/CI
- Add `packit watch-releases` to CLI.
- Add LocalProject type for CLI.
- Improve API.
- Add checkout_pr and checkout_release to Upstream class.
- Skip nonexisting files on upstream->downstream sync.
- Remove sync class.
- Remove transformator.
- Use refs instead of branch for LocalProject.
- Split Config and PackageConfig.
- Remove sync methods from the API.
- Introduce bot_api.


Patch:
diff --git a/packit/api.py b/packit/api.py
index 6186cf81..52fbe69b 100644
--- a/packit/api.py
+++ b/packit/api.py
@@ -1,88 +1,72 @@
 """
-This is the official python interface for source-git. This is used exclusively in the CLI.
+This is the official python interface for packit.
 """
 
 import logging
-from typing import Any, Dict
 
-import requests
+from rebasehelper.versioneer import versioneers_runner
 
+from packit.config import Config, PackageConfig
 from packit.distgit import DistGit
-from packit.fed_mes_consume import Consumerino
-from packit.sync import Synchronizer
 from packit.upstream import Upstream
-from packit.watcher import SourceGitCheckHelper
 
 logger = logging.getLogger(__name__)
 
 
 class PackitAPI:
-    def __init__(self, config):
-        # TODO: the url template should be configurable
-        self.datagrepper_url = (
-            "https://apps.fedoraproject.org/datagrepper/id?id={msg_id}&is_raw=true"
-        )
-        self.consumerino = Consumerino()
+    def __init__(self, config: Config, package_config: PackageConfig) -> None:
         self.config = config
+        self.package_config = package_config
 
-    def fetch_fedmsg_dict(self, msg_id: str) -> Dict[str, Any]:
-        """
-        Fetch selected message from datagrepper
+    def sync_pr(self, pr_id, dist_git_branch: str, dist_git_path: str = None):
+        up = Upstream(config=self.config, package_config=self.package_config)
 
-        :param msg_id: str
-        :return: dict, the fedmsg
-        """
-        logger.debug(f"Proccessing message: {msg_id}")
-        url = self.datagrepper_url.format(msg_id=msg_id)
-        response = requests.get(url)
-        msg_dict = response.json()
-        return msg_dict
+        dg = DistGit(
+            config=self.config,
+            package_config=self.package_config,
+            dist_git_path=dist_git_path,
+        )
 
-    def sync_upstream_pr_to_distgit(self, fedmsg_dict: Dict[str, Any]) -> None:
-        """
-        Take the input fedmsg (github push or pr create) and sync the content into dist-git
+        up.checkout_pr(pr_id=pr_id)
+        local_pr_branch = f"pull-request-{pr_id}-sync"
+        # fetch and reset --hard upstream/$branch?
+        dg.checkout_branch(dist_git_branch)
+        dg.create_branch(local_pr_branch)
+        dg.checkout_branch(local_pr_branch)
 
-        :param fedmsg_dict: dict, code change on github
-        """
-        logger.info("syncing the upstream code to downstream")
-        with Synchronizer(self.config) as sync:
-            sync.sync_using_fedmsg_dict(fedmsg_dict)
+        self.sync(
+            upstream=up,
+            distgit=dg,
+            commit_msg=[f"Sync upstream pr: {pr_id}", "more info"],
+            pr_title=f"Upstream pr: {pr_id}",
+            pr_description="description",
+        )
 
-    def keep_syncing_upstream_pulls(self) -> None:
-        """
-        Watch Fedora messages and keep syncing upstream PRs downstream. This runs forever.
+    def sync_release(
+        self, dist_git_branch: str, dist_git_path: str = None, version: str = None
+    ):
         """
-        with Synchronizer(self.config) as sync:
-            for topic, action, msg in self.consumerino.iterate_gh_pulls():
-                # TODO:
-                #   handle edited (what's that?)
-                #   handle closed (merged & not merged)
-                if action in ["opened", "synchronize", "reopened"]:
-                    sync.sync_using_fedmsg_dict(msg)
-
-    def process_ci_result(self, fedmsg_dict: Dict[str, Any]) -> None:
+        Update given package in Fedora
         """
-        Take the CI result, figure out if it's related to source-git and if it is, report back to upstream
+        up = Upstream(config=self.config, package_config=self.package_config)
 
-        :param fedmsg_dict: dict, flag added in pagure
-        """
-        sg = SourceGitCheckHelper(self.config)
-        sg.process_new_dg_flag(fedmsg_dict)
+        dg = DistGit(
+            config=self.config,
+            package_config=self.package_config,
+            dist_git_path=dist_git_path,
+        )
 
-    def keep_fwding_ci_results(self) -> None:
-        """
-        Watch Fedora messages and keep reporting CI results back to upstream PRs. This runs forever.
-        """
-        for topic, msg in self.consumerino.iterate_dg_pr_flags():
-            self.process_ci_result(msg)
+        full_version = (
+            version
+            or versioneers_runner.run(
+                versioneer=None,
+                package_name=self.package_config.metadata["package_name"],
+                category=None,
+            )
+            or up.specfile.get_full_version()
+        )
+        up.checkout_release(full_version)
 
-    def update(self, dist_git_branch: str):
-        """
-        Update given package in Fedora
-        """
-        dg = DistGit(self.config)
-        up = Upstream(self.config)
-        full_version = up.specfile.get_full_version()
         local_pr_branch = f"{full_version}-update"
         # fetch and reset --hard upstream/$branch?
         logger.info(f"using \"{dist_git_branch}\" dist-git branch")
@@ -90,19 +74,31 @@ class PackitAPI:
         dg.create_branch(local_pr_branch)
         dg.checkout_branch(local_pr_branch)
 
-        dg.sync_files(up.local_project)
-        archive = dg.download_upstream_archive()
-
-        dg.upload_to_lookaside_cache(archive)
-
-        dg.commit(f"{full_version} upstream release", "more info")
-        dg.push_to_fork(local_pr_branch)
-        dg.create_pull(
-            f"Update to upstream release {full_version}",
-            (
-                f"Upstream branch: {up.local_project.git_repo.active_branch}\n"
-                f"Upstream commit: {up.local_project.git_repo.head.commit}\n"
+        self.sync(
+            upstream=up,
+            distgit=dg,
+            commit_msg=[f"{full_version} upstream release", "more info"],
+            pr_title=f"Update to upstream release {full_version}",
+            pr_description=(
+                f"Upstream tag: {full_version}\n"
+                f"Upstream commit: {up.local_project.git_repo.ref}\n"
             ),
-            local_pr_branch,
-            dist_git_branch
+            dist_git_branch=dist_git_branch,
+        )
+
+    def sync(
+        self, upstream, distgit, commit_msg, pr_title, pr_description, dist_git_branch
+    ):
+        distgit.sync_files(upstream.local_project)
+        archive = distgit.download_upstream_archive()
+
+        distgit.upload_to_lookaside_cache(archive)
+
+        distgit.commit(*commit_msg)
+        distgit.push_to_fork(distgit.local_project.ref)
+        distgit.create_pull(
+            pr_title,
+            pr_description,
+            source_branch=distgit.local_project.ref,
+            target_branch=dist_git_branch,
         )
diff --git a/packit/bot_api.py b/packit/bot_api.py
new file mode 100644
index 00000000..8bc033f1
--- /dev/null
+++ b/packit/bot_api.py
@@ -0,0 +1,153 @@
+"""
+This is the python interface for packit used by ci/bots
+and other tools working with multiple packages.
+
+This API is built on top of the packit.api.
+"""
+import logging
+from functools import lru_cache
+from typing import Dict, Optional
+
+from ogr.services.pagure import PagureService
+
+from ogr.services.github import GithubService
+from packit.api import PackitAPI
+from packit.config import Config, PackageConfig, get_packit_config_from_repo
+from packit.fed_mes_consume import Consumerino
+from packit.watcher import SourceGitCheckHelper
+
+logger = logging.getLogger(__name__)
+
+
+class PackitBotAPI:
+    def __init__(self, config: Config) -> None:
+        self.config = config
+        self.consumerino = Consumerino()
+
+    @property
+    @lru_cache()
+    def _github_service(self):
+        return GithubService(token=self.config.github_token)
+
+    @property
+    @lru_cache()
+    def _pagure_service(self):
+        return PagureService(token=self.config.pagure_user_token)
+
+    def watch_upstream_pull_request(self):
+        for topic, action, msg in self.consumerino.iterate_pull_requests():
+            if action in ["opened", "synchronize", "reopened"]:
+                self.sync_upstream_pull_request_with_fedmsg(fedmsg=msg)
+
+    def sync_upstream_pull_request_with_fedmsg(self, fedmsg: Dict):
+        repo_name = fedmsg["msg"]["pull_request"]["head"]["repo"]["name"]
+        namespace = fedmsg["msg"]["pull_request"]["head"]["repo"]["owner"]["login"]
+        ref = fedmsg["msg"]["pull_request"]["head"]["ref"]
+        pr_id = fedmsg["msg"]["pull_request"]["id"]
+
+        github_repo = self._github_service.get_project(
+            repo=repo_name, namespace=namespace
+        )
+
+        package_config = get_packit_config_from_repo(
+            sourcegit_project=github_repo, ref=ref
+        )
+
+        if not package_config:
+            logger.debug(
+                f"No packit config: skipping pull-request {pr_id} for {namespace}/{repo_name}."
+            )
+            return
+        self.sync_upstream_pull_request(
+            package_config=package_config, pr_id=0, dist_git_branch=""
+        )
+
+    def sync_upstream_pull_request(
+        self, package_config: PackageConfig, pr_id: int, dist_git_branch: str
+    ):
+        logger.info("syncing the upstream code to downstream")
+        packit_api = PackitAPI(config=self.config, package_config=package_config)
+        packit_api.sync_pr(pr_id=pr_id, dist_git_branch=dist_git_branch)
+
+    def watch_upstream_release(self):
+        """
+        Listen on fedmsg and sync the upstream releases to the upstream pull-request.
+        """
+        for topic, msg in self.consumerino.iterate_releases():
+            self.sync_upstream_release_with_fedmsg(fedmsg=msg)
+
+    def sync_upstream_release_with_fedmsg(self, fedmsg: Dict):
+        """
+        Sync the upstream release to the distgit pull-request.
+
+        :param fedmsg: fedmsg dict
+        """
+        repo_name = fedmsg["msg"]["repository"]["name"]
+        namespace = fedmsg["msg"]["repository"]["owner"]["login"]
+        version = fedmsg["msg"]["release"]["tag_name"]
+
+        github_repo = self._github_service.get_project(
+            repo=repo_name, namespace=namespace
+        )
+
+        package_config = get_packit_config_from_repo(
+            sourcegit_project=github_repo, ref=version
+        )
+
+        if not package_config:
+            logger.debug(
+                f"No packit config: skipping release {version} for {namespace}/{repo_name}."
+            )
+            return
+
+        self.sync_upstream_release(
+            package_config=package_config, version=version, dist_git_branch=""
+        )
+
+    def sync_upstream_release(
+        self,
+        package_config: PackageConfig,
+        version: Optional[str],
+        dist_git_branch: str,
+    ):
+        """
+        Sync the upstream release to the distgit pull-request.
+
+        :param package_config: PackageConfig
+        :param version: not used now, str
+        :param dist_git_branch: str
+        """
+        logger.info("syncing the upstream code to downstream")
+        packit_api = PackitAPI(config=self.config, package_config=package_config)
+        packit_api.sync_release(dist_git_branch=dist_git_branch, version=version)
+
+    def watch_fedora_ci(self):
+        for topic, msg in self.consumerino.iterate_dg_pr_flags():
+            self.sync_fedora_ci_with_fedmsg(fedmsg=msg)
+
+    def sync_fedora_ci_with_fedmsg(self, fedmsg: Dict):
+        raise NotImplementedError(
+            "The watching of the Fedora CI is not implemented yet."
+        )
+
+        repo_name = fedmsg["msg"]["pull_request"]["project"]["name"]
+        namespace = fedmsg["msg"]["pull_request"]["project"]["namespace"]
+        pr_id = fedmsg["msg"]["pull_request"]["id"]
+
+        pagure_repo = self._pagure_service.get_project(
+            repo=repo_name, namespace=namespace
+        )
+
+        pull_request = pagure_repo.get_pr_info(pr_id=pr_id)
+
+        # TODO: Finish parsing fedmsg and call sync_fedora_ci
+
+    def sync_fedora_ci(self, package_config: PackageConfig):
+        raise NotImplementedError(
+            "The watching of the Fedora CI is not implemented yet."
+        )
+
+        # TODO: Rework the SourceGitCheckHelper and use it
+
+        sg = SourceGitCheckHelper(config=self.config, package_config=package_config)
+        sg.process_new_dg_flag(None)
diff --git a/packit/cli/packit_base.py b/packit/cli/packit_base.py
index 244784f3..c2853ad2 100644
--- a/packit/cli/packit_base.py
+++ b/packit/cli/packit_base.py
@@ -8,6 +8,7 @@ from packit.cli.sourcegit_to_dist_git import sg2dg
 from packit.cli.sourcegit_to_srpm import sg2srpm
 from packit.cli.watch_fedora_ci import watcher
 from packit.cli.watch_sg_pr import watch_pr
+from packit.cli.watch_upstream_release import watch_releases
 from packit.config import Config, get_context_settings
 from packit.utils import set_logging
 
@@ -44,7 +45,8 @@ packit_base.add_command(sg2srpm)
 packit_base.add_command(watcher)
 packit_base.add_command(version)
 packit_base.add_command(watch_pr)
+packit_base.add_command(watch_releases)
 packit_base.add_command(update)
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     packit_base()
diff --git a/packit/cli/types.py b/packit/cli/types.py
new file mode 100644
index 00000000..be229359
--- /dev/null
+++ b/packit/cli/types.py
@@ -0,0 +1,30 @@
+import os
+
+import click
+import requests
+
+from packit.local_project import LocalProject
+
+
+class LocalProjectParameter(click.ParamType):
+    """
+    Path or url.
+    """
+
+    name = "repo"
+
+    def convert(self, value, param, ctx):
+        try:
+            if os.path.isdir(value):
+                return LocalProject(working_dir=value)
+
+            try:
+                res = requests.get(value)
+                if res.ok:
+                    return LocalProject(git_url=value)
+                self.fail("Cannot connect to specified url.", param, ctx)
+            except requests.exceptions.BaseHTTPError as ex:
+                self.fail("Cannot connect to specified url.", param, ctx)
+
+        except ValueError as ex:
+            self.fail(ex, param, ctx)
diff --git a/packit/cli/update.py b/packit/cli/update.py
index ee44ff3f..1aca4b00 100644
--- a/packit/cli/update.py
+++ b/packit/cli/update.py
@@ -3,11 +3,13 @@ Update selected component from upstream in Fedora
 """
 
 import logging
+import os
 
 import click
 
 from packit.api import PackitAPI
-from packit.config import pass_config, get_context_settings
+from packit.cli.types import LocalProjectParameter
+from packit.config import pass_config, get_context_settings, get_local_package_config
 
 logger = logging.getLogger(__file__)
 
@@ -21,11 +23,12 @@ logger = logging.getLogger(__file__)
 @click.option("--dist-git-path",
               help="Path to dist-git repo to work in. "
                    "Otherwise clone the repo in a temporary directory.")
+@click.argument("repo", type=LocalProjectParameter(), default=os.path.abspath(os.path.curdir))
 @pass_config
-def update(config, dist_git_path, dist_git_branch):
+def update(config, dist_git_path, dist_git_branch, repo):
     """
     Release current upstream release into Fedora
     """
-    config.dist_git_path = dist_git_path
-    api = PackitAPI(config)
-    api.update(dist_git_branch)
+    package_config = get_local_package_config(directory=repo.working_dir)
+    api = PackitAPI(config=config, package_config=package_config)
+    api.sync_release(dist_git_branch, dist_git_path=dist_git_path)
diff --git a/packit/cli/watch_upstream_release.py b/packit/cli/watch_upstream_release.py
new file mode 100644
index 00000000..a1290f61
--- /dev/null
+++ b/packit/cli/watch_upstream_release.py
@@ -0,0 +1,30 @@
+#!/usr/bin/python3
+"""
+Watch for new upstream releases.
+"""
+import logging
+
+import click
+
+from packit.bot_api import PackitBotAPI
+from packit.config import pass_config
+
+logger = logging.getLogger(__name__)
+
+
+@click.command("watch-releases")
+@click.argument("message-id", nargs=-1)
+@pass_config
+def watch_releases(config, message_id):
+    """
+    watch for activity on github and create a downstream PR
+
+    :return: int, retcode
+    """
+    api = PackitBotAPI(config)
+    if message_id:
+        for msg_id in message_id:
+            fedmsg_dict = api.consumerino.fetch_fedmsg_dict(msg_id)
+            api.sync_upstream_release_with_fedmsg(fedmsg_dict)
+    else:
+        api.watch_upstream_release()
diff --git a/packit/config.py b/packit/config.py
index 510b475f..4eacdb1e 100644
--- a/packit/config.py
+++ b/packit/config.py
@@ -1,5 +1,3 @@
-from __future__ import annotations
-
 import json
 import logging
 import os
@@ -11,8 +9,8 @@ from typing import Optional, List
 import anymarkup
 import click
 from jsonschema import Draft4Validator
-from ogr.abstract import GitProject
 
+from ogr.abstract import GitProject
 from packit.constants import CONFIG_FILE_NAMES
 from packit.utils import exclude_from_dict
 
@@ -30,14 +28,6 @@ class Config:
         self._pagure_package_token = None
         self._pagure_fork_token = None
 
-        self.dist_git_path = None
-
-    @property
-    def package_config(self) -> PackageConfig:
-        if self._package_config is None:
-            self._package_config = get_local_package_config()
-        return self._package_config
-
     @property
     def github_token(self) -> str:
         if self._github_token is None:
@@ -98,7 +88,7 @@ class JobConfig:
     metadata: dict
 
     @classmethod
-    def get_from_dict(cls, raw_dict: dict, validate=True) -> JobConfig:
+    def get_from_dict(cls, raw_dict: dict, validate=True) -> "JobConfig":
         if validate and not JobConfig.is_dict_valid(raw_dict):
             raise Exception(f"Job config not valid.")
 
@@ -116,13 +106,13 @@ class JobConfig:
 
 class PackageConfig:
     def __init__(
-            self,
-            specfile_path: Optional[str] = None,
-            synced_files: Optional[List[str]] = None,
-            jobs: Optional[List[JobConfig]] = None,
-            metadata: Optional[dict] = None,
-            dist_git_namespace: str = "rpms",
-            upstream_project_url: str = ".",  # can be URL or path
+        self,
+        specfile_path: Optional[str] = None,
+        synced_files: Optional[List[str]] = None,
+        jobs: Optional[List[JobConfig]] = None,
+        metadata: Optional[dict] = None,
+        dist_git_namespace: str = "rpms",
+        upstream_project_url: str = ".",  # can be URL or path
     ):
         self.specfile_path: Optional[str] = specfile_path
         self.synced_files: Optional[List[str]] = synced_files
@@ -132,7 +122,7 @@ class PackageConfig:
         self.dist_git_namespace: str = dist_git_namespace
         self.upstream_project_url: str = upstream_project_url
 
-    def __eq__(self, other: PackageConfig):
+    def __eq__(self, other: "PackageConfig"):
         return (
             self.specfile_path == other.specfile_path
             and self.synced_files == other.synced_files
@@ -143,7 +133,7 @@ class PackageConfig:
         )
 
     @classmethod
-    def get_from_dict(cls, raw_dict: dict, validate=True) -> PackageConfig:
+    def get_from_dict(cls, raw_dict: dict, validate=True) -> "PackageConfig":
         if validate and not PackageConfig.is_dict_valid(raw_dict):
             raise Exception("Package config not valid.")
 
@@ -154,7 +144,9 @@ class PackageConfig:
         pc = PackageConfig(
             specfile_path=specfile_path,
             synced_files=synced_files,
-            jobs=[JobConfig.get_from_dict(raw_job, validate=False) for raw_job in raw_jobs],
+            jobs=[
+                JobConfig.get_from_dict(raw_job, validate=False) for raw_job in raw_jobs
+            ],
             metadata=metadata,
         )
 
@@ -189,22 +181,22 @@ def get_local_package_config(directory=None) -> Optional[PackageConfig]:
 
 
 def get_packit_config_from_repo(
-        sourcegit_project: GitProject, branch: str
+    sourcegit_project: GitProject, ref: str
 ) -> Optional[PackageConfig]:
     for config_file_name in CONFIG_FILE_NAMES:
         try:
             config_file = sourcegit_project.get_file_content(
-                path=config_file_name, ref=branch
+                path=config_file_name, ref=ref
             )
             logger.debug(
                 f"Found a config file '{config_file_name}' "
-                f"on branch '{branch}' "
+                f"on ref '{ref}' "
                 f"of the {sourcegit_project.full_repo_name} repository."
             )
         except FileNotFoundError:
             logger.debug(
                 f"The config file '{config_file_name}' "
-                f"not found on branch '{branch}' "
+                f"not found on ref '{ref}' "
                 f"of the {sourcegit_project.full_repo_name} repository."
             )
             continue
diff --git a/packit/distgit.py b/packit/distgit.py
index 11dd6673..2185974b 100644
--- a/packit/distgit.py
+++ b/packit/distgit.py
@@ -3,10 +3,10 @@ import os
 import shutil
 from typing import Optional, List
 
-from ogr.services.pagure import PagureService
 from rebasehelper.specfile import SpecFile
 
-from packit.config import Config
+from ogr.services.pagure import PagureService
+from packit.config import Config, PackageConfig
 from packit.local_project import LocalProject
 from packit.utils import FedPKG
 
@@ -25,8 +25,12 @@ class DistGit:
     The class works with a single instance of dist-git, that's the state, and methods of this class
     interact with the local copy.
     """
-    def __init__(self, config: Config):
+
+    def __init__(
+        self, config: Config, package_config: PackageConfig, dist_git_path: str = None
+    ):
         self.config = config
+        self.package_config = package_config
 
         self._local_project = None
 
@@ -34,12 +38,16 @@ class DistGit:
         self.pagure_user_token = self.config.pagure_user_token
         self.pagure_package_token = self.config.pagure_package_token
         self.pagure_fork_token = self.config.pagure_fork_token
-        self.package_name: Optional[str] = self.config.package_config.metadata.get('package_name', None)
+        self.package_name: Optional[str] = self.package_config.metadata.get(
+            "package_name", None
+        )
         self.fas_user = self.config.fas_user
-        self.dist_git_url: Optional[str] = self.config.package_config.metadata.get('dist_git_url', None)
-        self.files_to_sync: Optional[List[str]] = self.config.package_config.synced_files
-        self.dist_git_path = self.config.dist_git_path
-        self.dist_git_namespace: str = self.config.package_config.dist_git_namespace
+        self.dist_git_url: Optional[str] = self.package_config.metadata.get(
+            "dist_git_url", None
+        )
+        self.files_to_sync: Optional[List[str]] = self.package_config.synced_files
+        self.dist_git_path = dist_git_path
+        self.dist_git_namespace: str = self.package_config.dist_git_namespace
         self._specfile = None
 
     @property
@@ -58,7 +66,9 @@ class DistGit:
     @property
     def specfile_path(self) -> Optional[str]:
         if self.package_name:
-            return os.path.join(self.local_project.working_dir, f"{self.package_name}.spec")
+            return os.path.join(
+                self.local_project.working_dir, f"{self.package_name}.spec"
+            )
 
     @property
     def specfile(self):
@@ -122,11 +132,7 @@ class DistGit:
         )
 
     def create_pull(
-            self,
-            pr_title: str,
-            pr_description: str,
-            source_branch: str,
-            target_branch: str,
+        self, pr_title: str, pr_description: str, source_branch: str, target_branch: str
     ) -> None:
         """
         Create dist-git pull request using the requested branches
@@ -187,5 +193,8 @@ class DistGit:
         for fi in self.files_to_sync:
             fi = fi[1:] if fi.startswith("/") else fi
             src = os.path.join(upstream_project.working_dir, fi)
-            logger.info("syncing %s", src)
-            shutil.copy2(src, self.local_project.working_dir)
+            if os.path.exists(src):
+                logger.info("syncing %s", src)
+                shutil.copy2(src, self.local_project.working_dir)
+            else:
+                logger.debug("not found %s (no sync)", src)
diff --git a/packit/exceptions.py b/packit/exceptions.py
new file mode 100644
index 00000000..a1ce044f
--- /dev/null
+++ b/packit/exceptions.py
@@ -0,0 +1,6 @@
+class PackitException(Exception):
+    pass
+
+
+class PackitConfigException(PackitException):
+    pass
diff --git a/packit/fed_mes_consume.py b/packit/fed_mes_consume.py
index 212194f3..eee1984e 100644
--- a/packit/fed_mes_consume.py
+++ b/packit/fed_mes_consume.py
@@ -4,11 +4,13 @@ The code here handles receiving messages about events and has wrappers to proces
 This module is meant to be imported in API and should be independent.
 """
 import logging
+
 # not yet: https://github.com/fedora-infra/fedora-messaging/issues/111
 # from fedora_messaging import api
-from typing import Iterable, Tuple
+from typing import Iterable, Tuple, Dict, Any
 
 import fedmsg
+import requests
 
 logger = logging.getLogger(__name__)
 
@@ -18,11 +20,11 @@ class Consumerino:
     A class which provides an interface to consume messages via a callback
     """
 
-    def __init__(self) -> None:
-        """
-
-        """
-        pass
+    def __init__(self, url: str = None) -> None:
+        # TODO: the url template should be configurable
+        self.datagrepper_url = url or (
+            "https://apps.fedoraproject.org/datagrepper/id?id={msg_id}&is_raw=true"
+        )
         # timestamp = datetime.datetime.now().strftime("%Y%M%d-%H%M%S")
         # self.binding = {
         #     'exchange': 'amq.topic',  # The AMQP exchange to bind our queue to
@@ -36,7 +38,7 @@ class Consumerino:
     #     api.consume(callback, self.binding)
 
     @staticmethod
-    def iterate_gh_pulls() -> Iterable[Tuple[str, str, dict]]:
+    def iterate_pull_requests() -> Iterable[Tuple[str, str, dict]]:
         """
         Provide messages for all github pull-request-related events
 
@@ -55,6 +57,23 @@ class Consumerino:
                 action = topic.rsplit(".", 1)[1]
                 yield topic, action, msg
 
+    @staticmethod
+    def iterate_releases() -> Iterable[Tuple[str, dict]]:
+        """
+        Provide messages for all github pull-request-related events
+
+        Actions:
+            https://developer.github.com/v3/activity/events/types/#events-api-payload-28
+
+        :return: tuple, (full topic name, dict with the message)
+        """
+        # https://github.com/fedora-infra/github2fedmsg/blob/a9c178b93aa6890e6b050e5f1c5e3297ceca463c/github2fedmsg/views/webhooks.py#L120
+        topic = "org.fedoraproject.prod.github.release."
+
+        logger.info("listening on fedmsg, topic=%s", topic)
+        for name, endpoint, topic, msg in fedmsg.tail_messages(topic=topic):
+            yield topic, msg
+
     @staticmethod
     def iterate_dg_pr_flags() -> Iterable[Tuple[str, dict]]:
         """
@@ -69,3 +88,16 @@ class Consumerino:
         logger.info("listening on fedmsg, topic=%s", topic)
         for name, endpoint, topic, msg in fedmsg.tail_messages(topic=topic):
             yield topic, msg
+
+    def fetch_fedmsg_dict(self, msg_id: str) -> Dict[str, Any]:
+        """
+        Fetch selected message from datagrepper
+
+        :param msg_id: str
+        :return: dict, the fedmsg
+        """
+        logger.debug(f"Proccessing message: {msg_id}")
+        url = self.datagrepper_url.format(msg_id=msg_id)
+        response = requests.get(url)
+        msg_dict = response.json()
+        return msg_dict
diff --git a/packit/local_project.py b/packit/local_project.py
index 56ed86fd..879c0148 100644
--- a/packit/local_project.py
+++ b/packit/local_project.py
@@ -14,7 +14,7 @@ class LocalProject:
         self,
         git_repo: git.Repo = None,
         working_dir: str = None,
-        branch: str = None,
+        ref: str = None,
         git_project: GitProject = None,
         git_service: GitService = None,
         git_url: str = None,
@@ -25,7 +25,7 @@ class LocalProject:
 
         self.git_repo = git_repo
         self.working_dir = working_dir
-        self._branch = branch
+        self._ref = ref
         self.git_project = git_project
         self.git_service = git_service
         self.git_url = git_url
@@ -65,8 +65,11 @@ class LocalProject:
                 self.git_service = self.git_project.service
                 change = True
 
-            if self.git_repo and not self._branch:
-                self._branch = self.git_repo.active_branch
+            if self.git_repo and not self._ref:
+                if self.git_repo.head.is_detached:
+                    self._ref = self.git_repo.head.commit.hexsha
+                else:
+                    self._ref = self.git_repo.active_branch
                 change = True
 
             if self.git_repo and not self.working_dir:
@@ -74,7 +77,9 @@ class LocalProject:
                 change = True
 
             if self.working_dir and not self.git_repo:
-                logger.debug("working_dir is set and git_repo is not: let's discover...")
+                logger.debug(
+                    "working_dir is set and git_repo is not: let's discover..."
+                )
                 if is_git_repo(directory=self.working_dir):
                     self.git_repo = git.Repo(path=self.working_dir)
                     logger.debug("it's a git repo!")
@@ -83,7 +88,11 @@ class LocalProject:
                     self.git_repo = get_repo(
                         url=self.git_url, directory=self.working_dir
                     )
-                    logger.debug("we just cloned git repo %s to %s", self.git_url, self.working_dir)
+                    logger.debug(
+                        "we just cloned git repo %s to %s",
+                        self.git_url,
+                        self.working_dir,
+                    )
                     change = True
 
             if self.git_url and not self.working_dir and not self.git_repo:
@@ -112,12 +121,12 @@ class LocalProject:
                 logger.debug("remote url of the repo is %s", self.git_url)
                 change = True
 
-        if branch:
+        if ref:
 
-            if branch not in self.git_repo.branches:
-                self.git_repo.create_head(self._branch)
+            if ref not in self.git_repo.branches:
+                self.git_repo.create_head(self._ref)
 
-            self.git_repo.branches[self._branch].checkout()
+            self.git_repo.branches[self._ref].checkout()
 
     def clean(self):
         if self.working_dir_temporary:
@@ -126,9 +135,12 @@ class LocalProject:
             self.working_dir_temporary = False
 
     @property
-    def branch(self):
+    def ref(self):
         if self.git_repo:
-            return self.git_repo.active_branch
+            if self.git_repo.head.is_detached:
+                return self.git_repo.head.commit.hexsha
+            else:
+                return self.git_repo.active_branch
         return None
 
     def __del__(self):
diff --git a/packit/sync.py b/packit/sync.py
deleted file mode 100644
index 9259e9ad..00000000
--- a/packit/sync.py
+++ /dev/null
@@ -1,199 +0,0 @@
-from __future__ import annotations
-
-import json
-import logging
-
-from ogr.abstract import GitService
-from ogr.services.github import GithubService
-from ogr.services.pagure import PagureService
-
-from packit.config import (
-    PackageConfig,
-    get_packit_config_from_repo,
-    get_local_package_config,
-    Config)
-from packit.local_project import LocalProject
-from packit.transformator import Transformator
-from packit.utils import commits_to_nice_str, checkout_pr
-
-logger = logging.getLogger(__name__)
-
-
-# TODO: refactor this class, it's too complex
-class Synchronizer:
-    """
-    One instance can be used for multiple sync actions.
-
-    Contains api tokens.
-    """
-
-    def __init__(self, config: Config):
-        self.config = config
-        self.github_token = config.github_token
-        self.pagure_user_token = config.pagure_user_token
-        self.pagure_package_token = config.pagure_package_token
-        self.pagure_fork_token = config.pagure_fork_token
-
-    @property
-    def sourcegit_service(self) -> GitService:
-        return GithubService(token=self.github_token)
-
-    def get_packit_config(
-            self, namespace: str, repo: str, branch: str
-    ) -> PackageConfig:
-        github_repo = self.sourcegit_service.get_project(repo=repo, namespace=namespace)
-        return get_packit_config_from_repo(sourcegit_project=github_repo, branch=branch)
-
-    def sync_using_fedmsg_dict(self, fedmsg_dict: dict) -> None:
-        """
-        Sync the pr to the dist-git.
-
-        :param fedmsg_dict: dict, fedmsg of a newly opened PR
-        """
-        try:
-            target_url = fedmsg_dict["msg"]["pull_request"]["base"]["repo"]["html_url"]
-        except (KeyError, ValueError) as ex:
-            logger.debug("ex = %s", ex)
-            logger.error("invalid fedmsg format")
-            return
-
-        try:
-            package_config = get_local_package_config()
-            if package_config:
-                logger.debug("Using local package config.")
-            else:
-                package_config = self.get_packit_config(
-                    repo=fedmsg_dict["msg"]["pull_request"]["head"]["repo"]["name"],
-                    namespace=fedmsg_dict["msg"]["pull_request"]["head"]["repo"][
-                        "owner"
-                    ]["login"],
-                    branch=fedmsg_dict["msg"]["pull_request"]["head"]["ref"],
-                )
-
-        except Exception as ex:
-            logger.info("no source-git mapping for project %s", target_url)
-            return
-
-        try:
-            msg_id = fedmsg_dict["msg_id"]
-        except KeyError:
-            logger.error("provided message is not a fedmsg (missing msg_id)")
-            return
-        try:
-            nice_msg = json.dumps(fedmsg_dict, indent=4)
-            logger.debug(f"Processing fedmsg:\n{nice_msg}")
-            self.sync(
-                target_url=fedmsg_dict["msg"]["pull_request"]["base"]["repo"][
-                    "html_url"
-                ],
-                target_ref=fedmsg_dict["msg"]["pull_request"]["base"]["ref"],
-                full_name=fedmsg_dict["msg"]["pull_request"]["base"]["repo"][
-                    "full_name"
-                ],
-                top_commit=fedmsg_dict["msg"]["pull_request"]["head"]["sha"],
-                pr_id=fedmsg_dict["msg"]["pull_request"]["number"],
-                pr_url=fedmsg_dict["msg"]["pull_request"]["html_url"],
-                title=fedmsg_dict["msg"]["pull_request"]["title"],
-                package_config=package_config,
-            )
-        except ConnectionError as ex:
-            # TODO: Retry on connection error
-            logger.warning(f"Connection error on processing a msg {msg_id}")
-            logger.debug(str(ex))
-            return
-        except Exception as ex:
-            logger.warning(f"Error on processing a msg {msg_id}")
-            logger.debug(str(ex))
-            return
-
-    def sync(
-            self,
-            target_url: str,
-            target_ref: str,
-            full_name: str,
-            top_commit: str,
-            pr_id: int,
-            pr_url: str,
-            title: str,
-            package_config: PackageConfig,
-            repo_directory: str = None,
-    ):
-        """
-        synchronize selected source-git pull request to respective downstream dist-git repo via a pagure pull request
-
-        :param target_url:
-        :param target_ref:
-        :param full_name: str, name of the github repo (e.g. user-cont/source-git)
-        :param top_commit: str, commit hash of the top commit in source-git PR
-        :param pr_id:
-        :param pr_url:
-        :param title:
-        :param package_config: PackageConfig, configuration of the sg - dg mapping
-        :param repo_directory: use this directory instead of pulling the url
-        :return:
-        """
-        logger.info("starting sync for project %s", target_url)
-
-        sourcegit = LocalProject(
-            git_url=target_url, working_dir=repo_directory, full_name=full_name
-        )
-
-        distgit = LocalProject(
-            git_url=package_config.metadata["dist_git_url"],
-            branch=f"source-git-{pr_id}",
-            git_service=PagureService(token=self.pagure_fork_token),
-            namespace="rpms",
-            repo_name=package_config.metadata["package_name"],
-        )
-
-        checkout_pr(repo=sourcegit.git_repo, pr_id=pr_id)
-
-        with Transformator(
-                sourcegit=sourcegit, distgit=distgit, package_config=package_config
-        ) as transformator:
-            transformator.create_archive()
-            transformator.copy_synced_content_to_distgit_directory(
-                synced_files=package_config.synced_files
-            )
-            transformator.add_patches_to_specfile()
-
-            commits = transformator.get_commits_to_upstream(upstream=target_ref)
-            commits_nice_str = commits_to_nice_str(commits)
-
-            logger.debug(f"Commits in source-git PR:\n{commits_nice_str}")
-
-            msg = f"upstream commit: {top_commit}\n\nupstream repo: {target_url}"
-            transformator.commit_distgit(title=title, msg=msg)
-
-            project_fork = distgit.git_project.get_fork()
-            if not project_fork:
-                logger.info("Creating a fork.")
-                distgit.git_project.fork_create()
-                project_fork = distgit.git_project.get_fork()
-
-            transformator.push_to_distgit_fork(
-                project_fork=project_fork, branch_name=distgit.branch
-            )
-
-            transformator.reset_checks(
-                full_name,
-                pr_id,
-                github_token=self.github_token,
-                pagure_user_token=self.pagure_user_token,
-            )
-            transformator.update_or_create_dist_git_pr(
-                distgit.git_project,
-                pr_id,
-                pr_url,
-                top_commit,
-                title,
-                source_ref=distgit.branch,
-                pagure_fork_token=self.pagure_fork_token,
-                pagure_package_token=self.pagure_package_token,
-            )
-
-    def __enter__(self) -> Synchronizer:
-        return self
-
-    def __exit__(self, *args) -> None:
-        pass
diff --git a/packit/transformator.py b/packit/transformator.py
deleted file mode 100644
index 331f41ce..00000000
--- a/packit/transformator.py
+++ /dev/null
@@ -1,492 +0,0 @@
-from __future__ import annotations
-
-import logging
-import os
-import shutil
-from distutils.dir_util import copy_tree
-from functools import lru_cache
-from typing import List, Tuple
-
-import git
-from rebasehelper.specfile import SpecFile
-from rebasehelper.versioneer import versioneers_runner
-
-from ogr.abstract import GitProject
-from packit.config import PackageConfig
-from packit.constants import DG_PR_COMMENT_KEY_SG_PR, DG_PR_COMMENT_KEY_SG_COMMIT
-from packit.downstream_checks import get_check_by_name
-from packit.local_project import LocalProject
-from packit.utils import get_rev_list_kwargs, FedPKG, run_command
-from packit.watcher import SourceGitCheckHelper
-
-logger = logging.getLogger(__name__)
-
-
-# TODO: refactor this class, it's too complex
-class Transformator:
-    """
-    Describes a relation between a source-git and a dist-git repository.
-
-    Allows multiple actions and transformation operations of the repos.
-    """
-
-    def __init__(
-        self,
-        package_config: PackageConfig,
-        sourcegit: LocalProject,
-        distgit: LocalProject,
-        version: str = None,
-        fas_username: str = None,
-    ) -> None:
-
-        self.package_config = package_config
-        self.sourcegit = sourcegit
-        self.distgit = distgit
-
-        self._version = version
-        self.rev_list_option_args = get_rev_list_kwargs(
-            self.package_config.metadata.get("rev_list_option", ["first_parent"])
-        )
-
-        self.fas_username = fas_username
-
-        self.upstream_name = self.package_config.metadata["upstream_name"]
-        self.package_name = (
-            self.package_config.metadata["package_name"] or self.upstream_name
-        )
-
-        self._archive = None
-
-    @property
-    @lru_cache()
-    def source_specfile_path(self) -> str:
-        return os.path.join(
-            self.sourcegit.working_dir, self.package_config.specfile_path
-        )
-
-    @property
-    @lru_cache()
-    def dist_specfile_path(self) -> str:
-        return os.path.join(self.distgit.working_dir, f"{self.package_name}.spec")
-
-    @property
-    @lru_cache()
-    def archive(self) -> str:
-        """
-        Path of the archive generated from the source-git.
-        If not exists, the archive will be created in the destination directory.
-        """
-        if not self._archive:
-            self._archive = self.download_upstream_archive()
-        return self._archive
-
-    @property
-    @lru_cache()
-    def version(self) -> str:
-        return self._version or self.version_from_specfile
-
-    @property
-    @lru_cache()
-    def version_from_specfile(self) -> str:
-        """
-        Version extracted from the specfile.
-        """
-        version_raw = run_command(
-            cmd=[
-                "rpmspec",
-                "-q",
-                "--qf",
-                "'%{version}\\n'",
-                "--srpm",
-                self.source_specfile_path,
-            ],
-            output=True,
-            fail=True,
-        )
-        version = version_raw.strip("'\\\n")
-        return version
-
-    @property
-    @lru_cache()
-    def fedpkg(self) -> FedPKG:
-        """
-        Instance of the FedPKG class (wrapper on top of the fedpkg command.)
-        """
-        return FedPKG(
-            fas_username=self.fas_username,
-            repo_path=self.distgit.git_url,
-            directory=self.distgit.working_dir,
-        )
-
-    @property
-    def distgit_spec(self) -> SpecFile:
-        return SpecFile(
-            path=self.dist_specfile_path,
-            sources_location=self.distgit.working_dir,
-            changelog_entry=None,
-        )
-
-    def clean(self) -> None:
-        """
-        Clean te temporary dir.
-        """
-        pass
-
-    def save_archive(
-        self, path: str = None, release: str = None, name="{project}-{version}.tar.gz"
-    ):
-        """
-        Saves the release archive.
-        """
-
-        if release:
-            release = self.sourcegit.git_project.get_release(release)
-        else:
-            release = self.sourcegit.git_project.get_releases()[-1]
-
-        archive_name = name.format(project=self.upstream_name, version=self.version)
-        archive_path = path or os.path.join(self.distgit.working_dir, archive_name)
-        release.save_archive(path=archive_path)
-
-    def download_upstream_archive(self):
-        self.distgit_spec.download_remote_sources()
-        archive_name = self.distgit_spec.get_archive()
-        self._archive = os.path.join(self.distgit.working_dir, archive_name)
-        logger.info(f"Downloaded archive: {self._archive}")
-
-    def create_archive(
-        self, path: str = None, name="{project}-{version}.tar.gz"
-    ) -> str:
-        """
-        Create archive from the provided git repo. The archive needs to be able to be used to build
-        the project using rpmbuild command. The expectation is that the directory within the archive
-        should be named {project}-{version}.
-
-        :return: str, path to the archive
-        """
-        archive_name = name.format(project=self.upstream_name, version=self.version)
-        archive_path = path or os.path.join(self.distgit.working_dir, archive_name)
-
-        self.add_exclude_redhat_to_gitattributes()
-
-        with open(archive_path, "wb") as fp:
-            self.sourcegit.git_repo.archive(
-                fp,
-                prefix=f"./{self.upstream_name}-{self.version}/",
-                worktree_attributes=True,
-            )
-
-        logger.info(f"Archive created: {archive_path}")
-        return archive_path
-
-    def add_exclude_redhat_to_gitattributes(self) -> None:
-        """
-        Add a line to .gitattributes to export-ignore redhat dir.
-        TODO: We need to use upstream release archive directly
-        """
-        logger.debug("Adding 'redhat/ export-ignore' to .gitattributes")
-        gitattributes_path = os.path.join(self.sourcegit.working_dir, ".gitattributes")
-        with open(gitattributes_path, "a") as gitattributes_file:
-            for file in self.package_config.synced_files:
-                file_in_working_dir = os.path.join(self.sourcegit.working_dir, file)
-                if os.path.isdir(file_in_working_dir):
-                    gitattributes_file.writelines([f"{file}/ export-ignore\n"])
-                elif os.path.isfile(file_in_working_dir):
-                    gitattributes_file.writelines([f"{file} export-ignore\n"])
-
-    @lru_cache()
-    def create_srpm(self) -> str:
-        logger.debug("Start creating of the SRPM.")
-        if not self.archive:
-            logger.error("No source archive found.")
-            raise Exception("No source archive found.")
-        logger.debug(f"Using archive: {self.archive}")
-
-        output = run_command(
-            cmd=[
-                "rpmbuild",
-                "-bs",
-                f"{self.dist_specfile_path}",
-                "--define",
-                f"_sourcedir {self.distgit.working_dir}",
-                "--define",
-                f"_specdir {self.distgit.working_dir}",
-                "--define",
-                f"_buildir {self.distgit.working_dir}",
-                "--define",
-                f"_srcrpmdir {self.distgit.working_dir}",
-                "--define",
-                f"_rpmdir {self.distgit.working_dir}",
-            ],
-            fail=True,
-            output=True,
-        )
-        specfile_name = output.split(':')[1].rstrip()
-        logger.info(f"Specfile created: {specfile_name}")
-        return specfile_name
-
-    @lru_cache()
-    def get_commits_to_upstream(
-        self, upstream: str, add_usptream_head_commit=False
-    ) -> List[git.Commit]:
-        """
-        Return the list of commits from current branch to upstream rev/tag.
-
-        :param upstream: str -- git branch or tag
-        :return: list of commits (last commit on the current branch.).
-        """
-
-        if upstream in self.sourcegit.git_repo.tags:
-            upstream_ref = upstream
-        else:
-            upstream_ref = f"origin/{upstream}"
-            if upstream_ref not in self.sourcegit.git_repo.refs:
-                raise Exception(
-                    f"Upstream {upstream_ref} branch nor {upstream} tag not found."
-                )
-
-        commits = list(
-            self.sourcegit.git_repo.iter_commits(
-                rev=f"{upstream_ref}..{self.sourcegit._branch}",
-                reverse=True,
-                **self.rev_list_option_args,
-            )
-        )
-        if add_usptream_head_commit:
-            commits.insert(0, self.sourcegit.git_repo.refs[f"{upstream_ref}"].commit)
-
-        logger.debug(
-            f"Delta ({upstream_ref}..{self.sourcegit._branch}): {len(commits)}"
-        )
-        return commits
-
-    def create_patches(self, upstream: str = None) -> List[Tuple[str, str]]:
-        """
-        Create patches from downstream commits.
-
-        :param upstream: str -- git branch or tag
-        :return: [(patch_name, msg)] list of created patches (tuple of the file name and commit msg)
-        """
-
-        upstream = upstream or self.version_from_specfile
-        commits = self.get_commits_to_upstream(upstream, add_usptream_head_commit=True)
-        patch_list = []
-        for i, commit in enumerate(commits[1:]):
-            parent = commits[i]
-
-            patch_name = f"{i + 1:04d}-{commit.hexsha}.patch"
-            patch_path = os.path.join(self.distgit.working_dir, patch_name)
-            patch_msg = f"{commit.summary}\nAuthor: {commit.author.name} <{commit.author.email}>"
-
-            logger.debug(f"PATCH: {patch_name}\n{patch_msg}")
-            diff = run_command(
-                cmd=[
-                    "git",
-                    "diff",
-                    "--patch",
-                    parent.hexsha,
-                    commit.hexsha,
-                    "--",
-                    ".",
-                    '":(exclude)redhat"',
-                ],
-                cwd=self.sourcegit.working_dir,
-                output=True,
-            )
-
-            with open(patch_path, mode="w") as patch_file:
-                patch_file.write(diff)
-            patch_list.append((patch_name, patch_msg))
-
-        return patch_list
-
-    def add_patches_to_specfile(self, patch_list: List[Tuple[str, str]] = None) -> None:
-        """
-        Add the given list of (patch_name, msg) to the specfile.
-
-        :param patch_list: [(patch_name, msg)] if None, the patches will be generated
-        """
-        if patch_list is None:
-            patch_list = self.create_patches()
-
-        if not patch_list:
-            return
-
-        specfile_path = os.path.join(
-            self.distgit.working_dir, f"{self.package_name}.spec"
-        )
-
-        with open(file=specfile_path, mode="r+") as spec_file:
-            last_source_position = None
-            line = spec_file.readline()
-            while line:
-                if line.startswith("Source"):
-                    last_source_position = spec_file.tell()
-
-                line = spec_file.readline()
-
-            if not last_source_position:
-                raise Exception("Cannot found place for patches in specfile.")
-
-            spec_file.seek(last_source_position)
-            rest_of_the_file = spec_file.read()
-            spec_file.seek(last_source_position)
-
-            spec_file.write("\n\n# PATCHES FROM SOURCE GIT:\n")
-            for i, (patch, msg) in enumerate(patch_list):
-                commented_msg = "\n# " + "\n# ".join(msg.split("\n")) + "\n"
-                spec_file.write(commented_msg)
-                spec_file.write(f"Patch{i + 1:04d}: {patch}\n")
-
-            spec_file.write(rest_of_the_file)
-
-        logger.info(
-            f"Patches ({len(patch_list)}) added to the specfile ({specfile_path})"
-        )
-        self.sourcegit.git_repo.index.write()
-
-    def copy_synced_content_to_distgit_directory(self, synced_files: List[str]) -> None:
-        """
-        Copy files from source-git to destination directory.
-        """
-        for file in synced_files:
-            file_in_working_dir = os.path.join(self.sourcegit.working_dir, file)
-            logger.debug(f"Copying '{file_in_working_dir}' to distgit.")
-            if os.path.isdir(file_in_working_dir):
-                copy_tree(src=file_in_working_dir, dst=self.distgit.working_dir)
-            elif os.path.isfile(file_in_working_dir):
-                shutil.copy2(
-                    file_in_working_dir, os.path.join(self.distgit.working_dir, file)
-                )
-
-    def upload_archive_to_lookaside_cache(self, keytab: str) -> None:
-        """
-        Upload the archive to the lookaside cache using fedpkg.
-        (If not exists, the archive will be created.)
-        """
-        logger.info("Uploading the archive to lookaside cache.")
-        self.fedpkg.init_ticket(keytab)
-        self.fedpkg.new_sources(sources=self.archive, fail=False)
-
-    def commit_distgit(self, title: str, msg: str) -> None:
-        main_msg = f"[source-git] {title}"
-        self.distgit.git_repo.git.add("-A")
-        self.distgit.git_repo.index.write()
-        # TODO: implement signing properly: we need to create a cert for the bot, distribute it to the container,
-        #       prepare git config and then we can start signing
-        # TODO: make -s configurable
-        self.distgit.git_repo.git.commit("-s", "-m", main_msg, "-m", msg)
-
-    def reset_checks(
-        self, full_name: str, pr_id: int, github_token: str, pagure_user_token: str
-    ) -> None:
-        """
-        Before syncing a new change downstream, we need to reset status of checks
-        for all the configured tests
-        and wait for testing systems to get us the new ones.
-        """
-        sg = SourceGitCheckHelper(github_token, pagure_user_token)
-        for check_dict in self.package_config.metadata["checks"]:
-            check = get_check_by_name(check_dict["name"])
-            sg.set_init_check(full_name, pr_id, check)
-
-    def update_or_create_dist_git_pr(
-        self,
-        project: GitProject,
-        pr_id: int,
-        pr_url: str,
-        top_commit: str,
-        title: str,
-        source_ref: str,
-        pagure_fork_token: str,
-        pagure_package_token: str,
-    ) -> None:
-        # Sadly, pagure does not support editing initial comments of a PR via the API
-        # https://pagure.io/pagure/issue/4111
-        # Short-term solution: keep adding comments
-        # and get updated info about sg PR ID and commit desc
-        for pr in project.get_pr_list():
-
-            sg_pr_id_match = project.search_in_pr(
-                pr_id=pr.id,
-                filter_regex=DG_PR_COMMENT_KEY_SG_PR + r":\s*(\d+)",
-                reverse=True,
-                description=True,
-            )
-            if not sg_pr_id_match:
-                logger.debug(f"No automation comment found in dist-git PR: {pr.id}.")
-                continue
-
-            sg_pr_id = sg_pr_id_match[1]
-            if sg_pr_id_match[1] != str(pr_id):
-                logger.debug(
-                    f"Dist-git PR `{pr.id}` does not match " f"source-git PR `{pr_id}`."
-                )
-                continue
-
-            commit_match = project.search_in_pr(
-                pr_id=pr.id,
-                filter_regex=DG_PR_COMMENT_KEY_SG_COMMIT + r":\s*(\d+)",
-                reverse=True,
-                description=True,
-            )
-            if not commit_match:
-                logger.debug(
-                    f"Dist-git PR `{pr.id}` does not contain top-commit of the "
-                    f"source-git PR `{pr_id}`."
-                )
-                continue
-
-            logger.debug(f"Adding a new comment with update to existing PR.")
-            msg = (
-                f"New changes were pushed to the upstream pull request\n\n"
-                f"[{DG_PR_COMMENT_KEY_SG_PR}: {pr_id}]({pr_url})\n"
-                f"{DG_PR_COMMENT_KEY_SG_COMMIT}: {top_commit}"
-            )
-            # FIXME: consider storing the data above as a git note of the top commit
-            project.change_token(pagure_package_token)
-            project.pr_comment(pr.id, msg)
-            logger.info(f"new comment added on PR {pr.id} ({pr.url})")
-            break
-        else:
-            logger.debug(f"Matching dist-git PR not found => creating a new one.")
-            msg = (
-                f"This pull request contains changes from upstream "
-                f"and is meant to integrate them into Fedora\n\n"
-                f"[{DG_PR_COMMENT_KEY_SG_PR}: {pr_id}]({pr_url})\n"
-                f"{DG_PR_COMMENT_KEY_SG_COMMIT}: {top_commit}"
-            )
-            # This pagure call requires token from the package's FORK
-            project_fork = project.get_fork()
-            project_fork.change_token(pagure_fork_token)
-            dist_git_pr_id = project_fork.pr_create(
-                title=f"[source-git] {title}",
-                body=msg,
-                source_branch=source_ref,
-                target_branch="master",
-            ).id
-            logger.info(f"PR created: {dist_git_pr_id}")
-
-    def push_to_distgit_fork(self, project_fork, branch_name):
-        if "origin-fork" not in [
-            remote.name for remote in self.distgit.git_repo.remotes
-        ]:
-            self.distgit.git_repo.create_remote(
-                name="origin-fork", url=project_fork.get_git_urls()["ssh"]
-            )
-
-        # I suggest to comment this one while testing when the push is not needed
-        self.distgit.git_repo.remote("origin-fork").push(
-            refspec=branch_name, force=branch_name in project_fork.get_branches()
-        )
-
-    def __enter__(self) -> Transformator:
-        return self
-
-    def __exit__(self, *args) -> None:
-        self.clean()
-
-    def get_latest_upstream_version(self):
-        return versioneers_runner.run(
-            versioneer=None, package_name=self.package_name, category=None
-        )
diff --git a/packit/upstream.py b/packit/upstream.py
index 07b221ed..4b0d95ae 100644
--- a/packit/upstream.py
+++ b/packit/upstream.py
@@ -3,33 +3,38 @@ from typing import Optional
 
 from rebasehelper.specfile import SpecFile
 
-from packit.config import Config
+from packit.config import Config, PackageConfig
+from packit.exceptions import PackitException
 from packit.local_project import LocalProject
 
 
 class Upstream:
     """ interact with upstream project """
-    def __init__(self, config: Config):
+
+    def __init__(self, config: Config, package_config: PackageConfig):
         self.config = config
+        self.package_config = package_config
 
         self._local_project = None
         self._specfile = None
 
-        self.package_name: Optional[str] = self.config.package_config.metadata.get('package_name', None)
-        self.upstream_project_url: str = self.config.package_config.upstream_project_url
+        self.package_name: Optional[str] = self.package_config.metadata.get(
+            "package_name", None
+        )
+        self.upstream_project_url: str = self.package_config.upstream_project_url
 
     @property
     def specfile_path(self) -> Optional[str]:
         if self.package_name:
-            return os.path.join(self.local_project.working_dir, f"{self.package_name}.spec")
+            return os.path.join(
+                self.local_project.working_dir, f"{self.package_name}.spec"
+            )
 
     @property
     def local_project(self):
         """ return an instance of LocalProject """
         if self._local_project is None:
-            self._local_project = LocalProject(
-                working_dir=self.upstream_project_url
-            )
+            self._local_project = LocalProject(working_dir=self.upstream_project_url)
         return self._local_project
 
     @property
@@ -42,6 +47,23 @@ class Upstream:
             )
         return self._specfile
 
+    def checkout_pr(self, pr_id: int) -> None:
+        """
+        Checkout the branch for the pr.
+
+        TODO: Move this to ogr and make it compatible with other git forges.
+        """
+        self.local_project.git_repo.remote().fetch(
+            refspec=f"pull/{pr_id}/head:pull/{pr_id}"
+        )
+        self.local_project.git_repo.refs[f"pull/{pr_id}"].checkout()
+
+    def checkout_release(self, version: str) -> None:
+        try:
+            self.local_project.git_repo.git.checkout(version)
+        except Exception as ex:
+            raise PackitException("Cannot checkout release tag.", ex)
+
 
 class SourceGit(Upstream):
     pass
diff --git a/packit/watcher.py b/packit/watcher.py
index cff910e2..afbd7206 100644
--- a/packit/watcher.py
+++ b/packit/watcher.py
@@ -7,7 +7,7 @@ import github
 
 from ogr.services.pagure import PagureService
 
-from packit.config import Config
+from packit.config import Config, PackageConfig
 from packit.downstream_checks import DownstreamCheck
 
 logger = logging.getLogger(__name__)
@@ -18,11 +18,11 @@ class SourceGitCheckHelper:
     This class provides functionality to operate on github pull request checks
     """
 
-    def __init__(self, config: Config):
+    def __init__(self, config: Config, package_config: PackageConfig):
         self.config = config
-        self.pagure_token = config.pagure_user_token
-        self.github_token = config.github_token
-        self.gh = github.Github(login_or_token=self.github_token)
+        self.package_config = package_config
+        # TODO: Use OGR instead of the PyGitHub directly
+        self.gh = github.Github(login_or_token=self.config.github_token)
 
     def set_init_check(self, full_name: str, pr_id: int, check: DownstreamCheck):
         """
@@ -52,6 +52,10 @@ class SourceGitCheckHelper:
         :param msg:
         :return:
         """
+        raise NotImplementedError(
+            "The watching of the Fedora CI is not implemented yet."
+        )
+
         project_name = msg["msg"]["pullrequest"]["project"]["name"]
         logger.info("new flag for PR for %s", project_name)
 


Test patch:
diff --git a/tests/unit/test_local_project.py b/tests/unit/test_local_project.py
index c2102592..ea896b53 100644
--- a/tests/unit/test_local_project.py
+++ b/tests/unit/test_local_project.py
@@ -31,7 +31,7 @@ def test_local_project_working_dir_project():
     project = LocalProject(
         git_project=project_mock,
         working_dir=flexmock(),
-        git_repo=flexmock(active_branch="branch"),
+        git_repo=flexmock(active_branch="branch", head=flexmock(is_detached=False)),
     )
     assert project.git_service
     assert project.git_project
@@ -43,18 +43,26 @@ def test_local_project_working_dir_project():
 def test_local_project_repo_url():
     """Get working_dir from git_repo"""
     project = LocalProject(
-        git_repo=flexmock(active_branch="branch", working_dir="something"),
+        git_repo=flexmock(
+            active_branch="branch",
+            working_dir="something",
+            head=flexmock(is_detached=False),
+        ),
         git_url=flexmock(),
     )
     assert project.git_repo
     assert project.working_dir == "something"
-    assert project._branch == "branch"
+    assert project._ref == "branch"
 
 
 def test_local_project_repo():
     """Get git_url from git_repo"""
     project = LocalProject(
-        git_repo=flexmock(active_branch="branch", working_dir="something")
+        git_repo=flexmock(
+            active_branch="branch",
+            working_dir="something",
+            head=flexmock(is_detached=False),
+        )
         .should_receive("remote")
         .replace_with(lambda: flexmock(urls=["git/url"]))
         .once()
@@ -62,7 +70,7 @@ def test_local_project_repo():
     )
     assert project.git_repo
     assert project.working_dir == "something"
-    assert project._branch == "branch"
+    assert project._ref == "branch"
     assert project.git_url == "git/url"
 
 
@@ -79,12 +87,12 @@ def test_clone_project_checkout_branch():
                 .mock()
             },
         ),
-        branch="other",
+        ref="other",
         git_url=flexmock(),
     )
     assert project.git_repo
     assert project.working_dir == "something"
-    assert project._branch == "other"
+    assert project._ref == "other"
 
 
 def test_clone_project_checkout_new_branch():
@@ -92,7 +100,10 @@ def test_clone_project_checkout_new_branch():
     branches = {}
     project = LocalProject(
         git_repo=flexmock(
-            active_branch="branch", working_dir="something", branches=branches
+            active_branch="branch",
+            working_dir="something",
+            branches=branches,
+            head=flexmock(is_detached=False),
         )
         .should_receive("create_head")
         .with_args("other")
@@ -103,12 +114,12 @@ def test_clone_project_checkout_new_branch():
         )
         .once()
         .mock(),
-        branch="other",
+        ref="other",
         git_url=flexmock(),
     )
     assert project.git_repo
     assert project.working_dir == "something"
-    assert project._branch == "other"
+    assert project._ref == "other"
 
 
 def test_clone_project_service_repo_namespace():
@@ -123,7 +134,7 @@ def test_clone_project_service_repo_namespace():
         .mock(),
         git_url=flexmock(),
         working_dir=flexmock(),
-        git_repo=flexmock(active_branch=flexmock()),
+        git_repo=flexmock(active_branch=flexmock(), head=flexmock(is_detached=False)),
     )
     assert project.repo_name
     assert project.namespace
@@ -134,13 +145,19 @@ def test_clone_project_service_repo_namespace():
 def test_local_project_clone():
     flexmock(local_project).should_receive("get_repo").with_args(
         "http://some.example/url"
-    ).and_return(flexmock(working_dir="some/example/path", active_branch="branch"))
+    ).and_return(
+        flexmock(
+            working_dir="some/example/path",
+            active_branch="branch",
+            head=flexmock(is_detached=False),
+        )
+    )
 
     project = LocalProject(git_url="http://some.example/url")
 
     assert project.git_url
     assert project.git_repo
-    assert project.branch == "branch"
+    assert project.ref == "branch"
     assert project.working_dir_temporary
 
     project.working_dir_temporary = False
@@ -153,7 +170,9 @@ def test_local_project_repo_from_working_dir():
     flexmock(
         git,
         Repo=flexmock(
-            active_branch="branch", remote=lambda: flexmock(urls=["git/url"])
+            active_branch="branch",
+            remote=lambda: flexmock(urls=["git/url"]),
+            head=flexmock(is_detached=False),
         ),
     )
     project = LocalProject(working_dir="some/example/path")
@@ -161,7 +180,7 @@ def test_local_project_repo_from_working_dir():
     assert project.git_url == "git/url"
     assert project.git_repo
     assert project.git_repo.active_branch == "branch"
-    assert project.branch == "branch"
+    assert project.ref == "branch"
     assert not project.working_dir_temporary
 
 
@@ -169,7 +188,13 @@ def test_local_project_dir_url():
 
     flexmock(local_project).should_receive("get_repo").with_args(
         "http://some.example/url", "some/example/path"
-    ).and_return(flexmock(working_dir="some/example/path", active_branch="branch"))
+    ).and_return(
+        flexmock(
+            working_dir="some/example/path",
+            active_branch="branch",
+            head=flexmock(is_detached=False),
+        )
+    )
 
     project = LocalProject(
         git_url="http://some.example/url", working_dir="some/example/path"
@@ -177,7 +202,7 @@ def test_local_project_dir_url():
 
     assert project.git_url == "http://some.example/url"
     assert project.git_repo
-    assert project.branch == "branch"
+    assert project.ref == "branch"
     assert project.git_repo.active_branch == "branch"
     assert project.git_repo.working_dir == "some/example/path"
     assert not project.working_dir_temporary