Repository: iossifovlab/gpf

Issue:
[BUG] Building manifest with missing dvc controlled large files is broken
**What happened**:

When a GRR large resource files are managed by DVC, we use the `.dvc` files to get the md5 sums for these files. But miss the files themselves.

Let's say we have a resource without large DVC-managed files. We build the manifest of the resource. The large DVC-managed files will not be present in the manifest.

**What you expected to happen**:

When we build the manifest, we would like the large DVC-managed files to be listed in the manifest even if they are not present in the local resource directory.

**Minimal Complete Verifiable Example**:

- Clone the GRR repo from git@github.com:iossifovlab/grr.git
- Run `grr_manage resource-repair` for any of the resource
- Check the manifest to see that the DVC-managed files are missing in the manifest.

**Environment**:

- GPF version: development version 3.7dev from the master branch


Discussion:

Patch:
diff --git a/dae/dae/genomic_resources/cached_repository.py b/dae/dae/genomic_resources/cached_repository.py
index 00d4c0294..57253219b 100644
--- a/dae/dae/genomic_resources/cached_repository.py
+++ b/dae/dae/genomic_resources/cached_repository.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 import os
 import logging
 
-from typing import Iterable, Optional, Dict, Generator
+from typing import Iterable, Optional, Dict, Generator, cast
 from concurrent.futures import ThreadPoolExecutor, as_completed
 
 from .repository import GenomicResource, \
@@ -16,6 +16,7 @@ logger = logging.getLogger(__name__)
 
 
 class CacheResource(GenomicResource):
+    """Represents resources stored in cache."""
 
     def __init__(self, resource, protocol: CachingProtocol):
         super().__init__(
@@ -25,6 +26,10 @@ class CacheResource(GenomicResource):
             config=resource.config,
             manifest=resource.get_manifest())
 
+    def get_manifest(self) -> Manifest:
+        return cast(CachingProtocol, self.proto)\
+            .remote_protocol.get_manifest(self)
+
 
 class CachingProtocol(ReadOnlyRepositoryProtocol):
     """Defines caching GRR repository protocol."""
diff --git a/dae/dae/genomic_resources/cli.py b/dae/dae/genomic_resources/cli.py
index fe58d22ef..67dba7111 100644
--- a/dae/dae/genomic_resources/cli.py
+++ b/dae/dae/genomic_resources/cli.py
@@ -126,7 +126,7 @@ def _run_list_command(
         print(
             f"{res.get_type():20} {res.get_version_str():7s} "
             f"{len(list(res.get_manifest().get_files())):2d} {res_size:12d} "
-            f"{res.get_id()}")
+            f"{res.get_id()}", file=sys.stderr)
 
 
 def _configure_repo_init_subparser(subparsers):
@@ -258,7 +258,7 @@ def collect_dvc_entries(
         basename = os.path.basename(filename)
 
         if filename not in manifest:
-            logger.warning(
+            logger.info(
                 "filling manifest of <%s> with entry for <%s> based on "
                 "dvc data only",
                 res.resource_id, filename)
@@ -281,10 +281,9 @@ def _do_resource_manifest_command(proto, res, dry_run, force, use_dvc):
 
     manifest_update = proto.check_update_manifest(res, prebuild_entries)
     if not bool(manifest_update):
-        print(
-            f"manifest of <{res.get_genomic_resource_id_version()}> "
-            f"is up to date",
-            file=sys.stderr)
+        logger.info(
+            "manifest of <%s> is up to date",
+            res.get_genomic_resource_id_version())
     else:
         msg = \
             f"manifest of " \
@@ -296,26 +295,26 @@ def _do_resource_manifest_command(proto, res, dry_run, force, use_dvc):
             msg = f"{msg}; " \
                 f"entries to delete from manifest " \
                 f"{sorted(manifest_update.entries_to_delete)}"
-        print(msg, file=sys.stderr)
+        logger.info(msg)
 
     if dry_run:
-        return
+        return bool(manifest_update)
 
     if force:
-        print(
-            f"building manifest for resource <{res.resource_id}>...",
-            file=sys.stderr)
+        logger.info(
+            "building manifest for resource <%s>...", res.resource_id)
         manifest = proto.build_manifest(
             res, prebuild_entries)
         proto.save_manifest(res, manifest)
+        return True
 
     elif bool(manifest_update):
-        print(
-            f"updating manifest for resource <{res.resource_id}>...",
-            file=sys.stderr)
+        logger.info(
+            "updating manifest for resource <%s>...", res.resource_id)
         manifest = proto.update_manifest(
             res, prebuild_entries)
         proto.save_manifest(res, manifest)
+    return bool(manifest_update)
 
 
 def _run_repo_manifest_command(proto, **kwargs):
@@ -330,7 +329,8 @@ def _run_repo_manifest_command(proto, **kwargs):
     for res in proto.get_all_resources():
         _do_resource_manifest_command(proto, res, dry_run, force, use_dvc)
 
-    proto.build_content_file()
+    if not dry_run:
+        proto.build_content_file()
 
 
 def _find_resource(proto, repo_url, **kwargs):
@@ -378,10 +378,10 @@ def _do_resource_hist_command(  # pylint: disable=too-many-arguments
         client, proto, res, dry_run, force, use_dvc, region_size):
     if res.get_type() not in {
             "position_score", "np_score", "allele_score"}:
-        print(
-            f"skip histograms update for {res.resource_id}; "
-            f"not a score", file=sys.stderr)
+        logger.info(
+            "skip histograms update for %s; not a score", res.resource_id)
         return
+
     builder = HistogramBuilder(res)
     if dry_run:
         builder.check_update()
@@ -403,6 +403,7 @@ def _do_resource_hist_command(  # pylint: disable=too-many-arguments
     prebuild_entries = {}
     if use_dvc:
         prebuild_entries = collect_dvc_entries(proto, res)
+
     proto.save_manifest(
         res,
         proto.update_manifest(res, prebuild_entries))
@@ -463,10 +464,16 @@ def _run_repo_repair_command(proto, region_size, **kwargs):
         sys.exit(1)
     with dask_client as client:
         for res in proto.get_all_resources():
-            _do_resource_manifest_command(
+            status = _do_resource_manifest_command(
                 proto, res, dry_run, force, use_dvc)
-            _do_resource_hist_command(
-                client, proto, res, dry_run, force, use_dvc, region_size)
+            if dry_run and status:
+                logger.info(
+                    "manfiest %s needs update; can't check histograms",
+                    res.resource_id)
+            else:
+                _do_resource_hist_command(
+                    client, proto, res, dry_run, force, use_dvc, region_size)
+
     if not dry_run:
         proto.build_content_file()
 
@@ -490,10 +497,15 @@ def _run_resource_repair_command(proto, repo_url, region_size, **kwargs):
         sys.exit(1)
 
     with dask_client as client:
-        _do_resource_manifest_command(
+        status = _do_resource_manifest_command(
             proto, res, dry_run, force, use_dvc)
-        _do_resource_hist_command(
-            client, proto, res, dry_run, force, use_dvc, region_size)
+        if dry_run and status:
+            logger.info(
+                "manfiest %s needs update; can't check histograms",
+                res.resource_id)
+        else:
+            _do_resource_hist_command(
+                client, proto, res, dry_run, force, use_dvc, region_size)
 
 
 def _run_repo_info_command(proto, **kwargs):  # pylint: disable=unused-argument
@@ -613,7 +625,7 @@ def cli_manage(cli_args=None):
                 "Can't find repository starting from: %s", os.getcwd())
             sys.exit(1)
         repo_url = str(repo_url)
-        print("working with repository:", repo_url)
+        print(f"working with repository: {repo_url}", file=sys.stderr)
 
     proto = _create_proto(repo_url, args.extra_args)
     if command == "list":
diff --git a/dae/dae/genomic_resources/fsspec_protocol.py b/dae/dae/genomic_resources/fsspec_protocol.py
index 11a023ccb..7d5c89ae6 100644
--- a/dae/dae/genomic_resources/fsspec_protocol.py
+++ b/dae/dae/genomic_resources/fsspec_protocol.py
@@ -440,7 +440,6 @@ class FsspecReadWriteProtocol(
             md5sum=md5)
 
         self.save_resource_file_state(dest_resource, state)
-        self.save_manifest(dest_resource, remote_manifest)
 
         return state
 
diff --git a/dae/dae/genomic_resources/histogram.py b/dae/dae/genomic_resources/histogram.py
index 2535cc089..d829cfa0b 100644
--- a/dae/dae/genomic_resources/histogram.py
+++ b/dae/dae/genomic_resources/histogram.py
@@ -6,7 +6,6 @@ from __future__ import annotations
 
 import hashlib
 import os
-import sys
 import logging
 from typing import Dict, Any
 from copy import copy, deepcopy
@@ -253,9 +252,10 @@ class HistogramBuilder:
             actual_md5 = metadata.get(score_id, {}).get("md5", None)
             expected_md5 = hashes.get(score_id, None)
             if actual_md5 == expected_md5:
-                logger.info("Skipping calculation of score "
-                            "%s as it's already calculated",
-                            hist_conf["score"])
+                logger.debug(
+                    "Skipping calculation of score "
+                    "%s as it's already calculated",
+                    hist_conf["score"])
                 loaded_hists[score_id] = hists[score_id]
             else:
                 configs_to_calculate.append(hist_conf)
@@ -266,17 +266,14 @@ class HistogramBuilder:
         _, configs_to_calculate = \
             self._collect_histograms_to_build(path)
         if configs_to_calculate:
-            print(
-                f"resource <"
-                f"{self.resource.get_genomic_resource_id_version()}> "
-                f"histograms {configs_to_calculate} need update",
-                file=sys.stderr)
+            logger.info(
+                "resource <%s> histograms %s need update",
+                self.resource.get_genomic_resource_id_version(),
+                configs_to_calculate)
         else:
-            print(
-                f"histograms of <"
-                f"{self.resource.get_genomic_resource_id_version()}> "
-                f"are up to date",
-                file=sys.stderr)
+            logger.info(
+                "histograms of <%s> are up to date",
+                self.resource.get_genomic_resource_id_version())
         return configs_to_calculate
 
     def update(
diff --git a/dae/dae/genomic_resources/repository.py b/dae/dae/genomic_resources/repository.py
index 76c13db04..6f9ee4f01 100644
--- a/dae/dae/genomic_resources/repository.py
+++ b/dae/dae/genomic_resources/repository.py
@@ -545,8 +545,8 @@ class ReadWriteRepositoryProtocol(ReadOnlyRepositoryProtocol):
             manifest.add(entry)
             state = self.load_resource_file_state(resource, entry.name)
             if state is None:
-                entries_to_update.add(entry.name)
-                continue
+                state = self.build_resource_file_state(resource, entry.name)
+                self.save_resource_file_state(resource, state)
             if state.filename not in current_manifest:
                 entries_to_update.add(entry.name)
                 continue
@@ -608,7 +608,6 @@ class ReadWriteRepositoryProtocol(ReadOnlyRepositoryProtocol):
             return manifest
         except FileNotFoundError:
             manifest = self.build_manifest(resource)
-            self.save_manifest(resource, manifest)
             return manifest
 
     @abc.abstractmethod


Test patch:
diff --git a/dae/conftest.py b/dae/conftest.py
index b4a2e6044..b6761ba7b 100644
--- a/dae/conftest.py
+++ b/dae/conftest.py
@@ -34,7 +34,6 @@ class DummyClient:
     """Dummy Dask client for testing."""
 
     def __init__(self, **kwargs):
-        print("DUMMY CLIENT CREATED...")
         self.tasks = []
 
     @staticmethod
diff --git a/dae/dae/genomic_resources/tests/test_cli.py b/dae/dae/genomic_resources/tests/test_cli.py
index b8ea284ab..b3636af35 100644
--- a/dae/dae/genomic_resources/tests/test_cli.py
+++ b/dae/dae/genomic_resources/tests/test_cli.py
@@ -63,8 +63,8 @@ def test_cli_list(repo_fixture, tmp_path, capsys):
     cli_manage(["list", "-R", str(tmp_path)])
     out, err = capsys.readouterr()
 
-    assert err == ""
-    assert out == \
+    assert out == ""
+    assert err == \
         "Basic                0        2            7 one\n" \
         "gene_models          1.0      2           50 sub/two\n"
 
@@ -81,8 +81,8 @@ def test_cli_list_without_repo_argument(
 
     # Then
     out, err = capsys.readouterr()
-    assert err == ""
-    assert out == \
+    assert out == ""
+    assert err == \
         f"working with repository: {str(tmp_path)}\n" \
         "Basic                0        2            7 one\n" \
         "gene_models          1.0      2           50 sub/two\n"
diff --git a/dae/dae/genomic_resources/tests/test_cli_browse.py b/dae/dae/genomic_resources/tests/test_cli_browse.py
index 7e1787b9e..ce0efef7e 100644
--- a/dae/dae/genomic_resources/tests/test_cli_browse.py
+++ b/dae/dae/genomic_resources/tests/test_cli_browse.py
@@ -53,8 +53,8 @@ def test_cli_browse_with_grr_argument(repo_fixture, repo_def, capsys):
     cli_browse(["--grr", str(repo_def)])
     out, err = capsys.readouterr()
 
-    assert err == ""
-    assert out == \
+    assert out == ""
+    assert err == \
         "Basic                0        2            7 one\n" \
         "gene_models          1.0      2           50 sub/two\n"
 
@@ -67,8 +67,8 @@ def test_cli_browse_with_env_variable(repo_fixture, repo_def, capsys, mocker):
     cli_browse([])
     out, err = capsys.readouterr()
 
-    assert err == ""
-    assert out == \
+    assert out == ""
+    assert err == \
         "Basic                0        2            7 one\n" \
         "gene_models          1.0      2           50 sub/two\n"
 
@@ -81,7 +81,7 @@ def test_cli_browse_default_defintion(repo_fixture, repo_def, capsys, mocker):
     cli_browse([])
     out, err = capsys.readouterr()
 
-    assert err == ""
-    assert out == \
+    assert out == ""
+    assert err == \
         "Basic                0        2            7 one\n" \
         "gene_models          1.0      2           50 sub/two\n"
diff --git a/dae/dae/genomic_resources/tests/test_cli_hist.py b/dae/dae/genomic_resources/tests/test_cli_hist.py
index 994f0392d..78c2473f4 100644
--- a/dae/dae/genomic_resources/tests/test_cli_hist.py
+++ b/dae/dae/genomic_resources/tests/test_cli_hist.py
@@ -1,5 +1,6 @@
 # pylint: disable=W0621,C0114,C0116,W0212,W0613
 import os
+import logging
 import textwrap
 
 import pytest
@@ -108,41 +109,54 @@ def test_repo_histograms_dry_run(proto_fixture, dask_mocker, tmp_path):
 
 
 def test_resource_histograms_need_update_message(
-        proto_fixture, dask_mocker, tmp_path, capsys):
+        proto_fixture, dask_mocker, tmp_path, capsys, caplog):
 
     # Given
     assert not (tmp_path / "one/histograms").exists()
 
     # When
-    cli_manage([
-        "resource-histograms", "--dry-run", "-R", str(tmp_path), "-r", "one"])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "resource-histograms", "--dry-run",
+            "-R", str(tmp_path), "-r", "one"])
 
     # Then
     captured = capsys.readouterr()
-    assert captured.err == \
-        "resource <one> histograms " \
-        "[{'score': 'phastCons100way', 'bins': 100}] need update\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("dae.genomic_resources.histogram",
+         logging.INFO,
+         "resource <one> histograms "
+         "[{'score': 'phastCons100way', 'bins': 100}] need update"),
+    ]
 
 
 def test_repo_histograms_need_update_message(
-        proto_fixture, dask_mocker, tmp_path, capsys):
+        proto_fixture, dask_mocker, tmp_path, capsys, caplog):
 
     # Given
     assert not (tmp_path / "one/histograms").exists()
 
     # When
-    cli_manage([
-        "repo-histograms", "--dry-run", "-R", str(tmp_path)])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "repo-histograms", "--dry-run", "-R", str(tmp_path)])
 
     # Then
     captured = capsys.readouterr()
-    assert captured.err == \
-        "resource <one> histograms " \
-        "[{'score': 'phastCons100way', 'bins': 100}] need update\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("dae.genomic_resources.histogram",
+         logging.INFO,
+         "resource <one> histograms "
+         "[{'score': 'phastCons100way', 'bins': 100}] need update"),
+    ]
 
 
 def test_resource_histograms_no_update_message(
-        proto_fixture, dask_mocker, tmp_path, capsys):
+        proto_fixture, dask_mocker, tmp_path, capsys, caplog):
     # Given
     assert not (tmp_path / "one/histograms").exists()
     cli_manage([
@@ -151,17 +165,23 @@ def test_resource_histograms_no_update_message(
     _, _ = capsys.readouterr()
 
     # When
-    cli_manage([
-        "resource-histograms", "--dry-run", "-R", str(tmp_path), "-r", "one"])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "resource-histograms", "--dry-run",
+            "-R", str(tmp_path), "-r", "one"])
 
     # Then
-    _, err = capsys.readouterr()
-    assert err == \
-        "histograms of <one> are up to date\n"
+    out, err = capsys.readouterr()
+    assert out == ""
+    assert err == ""
+    assert caplog.record_tuples == [
+        ("dae.genomic_resources.histogram", logging.INFO,
+         "histograms of <one> are up to date")
+    ]
 
 
 def test_repo_histograms_no_update_message(
-        proto_fixture, dask_mocker, tmp_path, capsys):
+        proto_fixture, dask_mocker, tmp_path, capsys, caplog):
     # Given
     assert not (tmp_path / "one/histograms").exists()
     cli_manage([
@@ -170,10 +190,15 @@ def test_repo_histograms_no_update_message(
     _, _ = capsys.readouterr()
 
     # When
-    cli_manage([
-        "repo-histograms", "--dry-run", "-R", str(tmp_path)])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "repo-histograms", "--dry-run", "-R", str(tmp_path)])
 
     # Then
-    _, err = capsys.readouterr()
-    assert err == \
-        "histograms of <one> are up to date\n"
+    out, err = capsys.readouterr()
+    assert out == ""
+    assert err == ""
+    assert caplog.record_tuples == [
+        ("dae.genomic_resources.histogram", logging.INFO,
+         "histograms of <one> are up to date")
+    ]
diff --git a/dae/dae/genomic_resources/tests/test_cli_info.py b/dae/dae/genomic_resources/tests/test_cli_info.py
new file mode 100644
index 000000000..4e17186f2
--- /dev/null
+++ b/dae/dae/genomic_resources/tests/test_cli_info.py
@@ -0,0 +1,108 @@
+# pylint: disable=W0621,C0114,C0116,W0212,W0613
+import textwrap
+
+import pytest
+
+from dae.genomic_resources.cli import cli_manage
+from dae.genomic_resources.repository import GR_CONF_FILE_NAME
+from dae.genomic_resources.testing import build_testing_protocol, \
+    tabix_to_resource
+
+
+@pytest.fixture
+def proto_fixture(tmp_path, tabix_file):
+    proto = build_testing_protocol(
+        scheme="file",
+        root_path=str(tmp_path),
+        content={
+            "one": {
+                GR_CONF_FILE_NAME: textwrap.dedent("""
+                    type: position_score
+                    table:
+                        filename: data.bgz
+                    scores:
+                        - id: phastCons100way
+                          type: float
+                          name: s1
+                    histograms:
+                        - score: phastCons100way
+                          bins: 100
+                    """),
+            },
+            "two": {
+                GR_CONF_FILE_NAME: textwrap.dedent("""
+                    type: allele_score
+                    table:
+                        filename: data.bgz
+                        format: tabix
+                    scores:
+                        - id: AC
+                          type: int
+                          name: AC
+                    """),
+            }
+        })
+    resource = proto.get_resource("one")
+    tabix_to_resource(
+        tabix_file(
+            """
+            #chrom  pos_begin  pos_end  s1    s2
+            1       10         15       0.02  1.02
+            1       17         19       0.03  1.03
+            1       22         25       0.04  1.04
+            2       5          80       0.01  2.01
+            2       10         11       0.02  2.02
+            """, seq_col=0, start_col=1, end_col=2),
+        resource, "data.bgz"
+    )
+    resource = proto.get_resource("two")
+    tabix_to_resource(
+        tabix_file(
+            """
+            #CHROM  POS    chrom  variant    REF  ALT  AC
+            1       12198  1      sub(G->C)  G    C    0
+            1       12237  1      sub(G->A)  G    A    0
+            1       12259  1      sub(G->C)  G    C    0
+            1       12266  1      sub(G->A)  G    A    0
+            1       12272  1      sub(G->A)  G    A    0
+            1       12554  1      sub(A->G)  A    G    0
+            """, seq_col=0, start_col=1, end_col=1),
+        resource, "data.bgz"
+    )
+    return proto
+
+
+def test_resource_info(proto_fixture, dask_mocker, tmp_path):
+    assert not (tmp_path / "one/index.html").exists()
+
+    cli_manage(["resource-info", "-R", str(tmp_path), "-r", "one"])
+
+    assert (tmp_path / "one/index.html").exists()
+    assert not (tmp_path / "two/index.html").exists()
+    assert not (tmp_path / "index.html").exists()
+
+    cli_manage(["resource-info", "-R", str(tmp_path), "-r", "two"])
+
+    assert (tmp_path / "one/index.html").exists()
+    assert (tmp_path / "two/index.html").exists()
+    assert not (tmp_path / "index.html").exists()
+
+    with open(tmp_path / "one/index.html") as infile:
+        result = infile.read()
+
+    assert result.find("<h1>one</h1>")
+    assert result.find("<h3>Score file:</h3>")
+    assert result.find("<h3>Score definitions:</h3>")
+    assert result.find("<p>Score ID: phastCons100way</p>")
+    assert result.find("<h3>Histograms:</h3>")
+    print(result)
+
+
+def test_repo_info(proto_fixture, dask_mocker, tmp_path):
+    assert not (tmp_path / "one/index.html").exists()
+
+    cli_manage(["repo-info", "-R", str(tmp_path)])
+
+    assert (tmp_path / "one/index.html").exists()
+    assert (tmp_path / "two/index.html").exists()
+    assert (tmp_path / "index.html").exists()
diff --git a/dae/dae/genomic_resources/tests/test_cli_manifest.py b/dae/dae/genomic_resources/tests/test_cli_manifest.py
index 06d90995e..da5d3e44f 100644
--- a/dae/dae/genomic_resources/tests/test_cli_manifest.py
+++ b/dae/dae/genomic_resources/tests/test_cli_manifest.py
@@ -1,5 +1,5 @@
 # pylint: disable=W0621,C0114,C0116,W0212,W0613
-
+import logging
 import pytest
 
 from dae.genomic_resources.cli import cli_manage
@@ -31,6 +31,21 @@ def test_resource_manifest_simple(proto_fixture, tmp_path):
     assert not (tmp_path / GR_CONTENTS_FILE_NAME).exists()
 
 
+def test_resource_manifest_dry_run_simple(proto_fixture, tmp_path):
+    # Given
+    proto_fixture.filesystem.delete(str(tmp_path / "one/.MANIFEST"))
+
+    assert not (tmp_path / GR_CONTENTS_FILE_NAME).exists()
+    assert not (tmp_path / "one/.MANIFEST").exists()
+
+    # When
+    cli_manage([
+        "resource-manifest", "-R", str(tmp_path), "-r", "one", "--dry-run"])
+
+    # Then
+    assert not (tmp_path / "one/.MANIFEST").exists()
+
+
 def test_repo_manifest_simple(proto_fixture, tmp_path):
     # Given
     proto_fixture.filesystem.delete(str(tmp_path / "one/.MANIFEST"))
@@ -47,26 +62,56 @@ def test_repo_manifest_simple(proto_fixture, tmp_path):
     assert (tmp_path / GR_CONTENTS_FILE_NAME).exists()
 
 
-def test_repo_manifest_no_agruments(proto_fixture, tmp_path, mocker, capsys):
+def test_repo_manifest_dry_run_simple(proto_fixture, tmp_path):
     # Given
+    proto_fixture.filesystem.delete(str(tmp_path / "one/.MANIFEST"))
+
+    assert not (tmp_path / GR_CONTENTS_FILE_NAME).exists()
+    assert not (tmp_path / "one/.MANIFEST").exists()
+
+    # When
     cli_manage([
-        "repo-manifest", "-R", str(tmp_path)])
+        "repo-manifest", "-R", str(tmp_path), "--dry-run"])
+
+    # Then
+    assert not (tmp_path / "one/.MANIFEST").exists()
+
+
+def test_repo_manifest_no_agruments(
+        proto_fixture, tmp_path, mocker, capsys, caplog):
+    # Given
+    cli_manage([
+        "-VV", "repo-manifest", "-R", str(tmp_path)])
     mocker.patch("os.getcwd", return_value=str(tmp_path))
     capsys.readouterr()
 
     # When
-    cli_manage(["repo-manifest"])
+    with caplog.at_level(logging.INFO):
+        cli_manage(["repo-manifest"])
 
     # Then
     out, err = capsys.readouterr()
 
-    assert out.startswith(f"working with repository: {tmp_path}\n")
+    assert out == ""
     assert err == \
-        "manifest of <one> is up to date\n" \
-        "manifest of <sub/two> is up to date\n" \
-        "manifest of <sub/two(1.0)> is up to date\n" \
-        "manifest of <three(2.0)> is up to date\n" \
-        "manifest of <xxxxx-genome> is up to date\n"
+        f"working with repository: {tmp_path}\n"
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> is up to date"),
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <sub/two> is up to date"),
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <sub/two(1.0)> is up to date"),
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <three(2.0)> is up to date"),
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <xxxxx-genome> is up to date"),
+    ]
 
 
 def test_check_manifest_update(proto_fixture, tmp_path):
@@ -143,64 +188,86 @@ def test_repo_dry_run_manifest_update(proto_fixture, tmp_path):
 
 
 def test_resource_dry_run_manifest_needs_update_message(
-        proto_fixture, tmp_path, capsys):
+        proto_fixture, tmp_path, capsys, caplog):
     # Given
     res = proto_fixture.get_resource("one")
     with res.open_raw_file("data.txt", "wt") as outfile:
         outfile.write("alabala2")
 
     # When
-    cli_manage([
-        "resource-manifest", "--dry-run", "-R", str(tmp_path), "-r", "one"])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "resource-manifest", "--dry-run",
+            "-R", str(tmp_path), "-r", "one"])
 
     # Then
+    assert bool(proto_fixture.check_update_manifest(res))
+
     captured = capsys.readouterr()
-    print(captured.err)
-    assert captured.err == \
-        "manifest of <one> should be updated; " \
-        "entries to update in manifest ['data.txt']\n"
+    assert captured.out == ""
+    assert captured.err == ""
+
+    assert caplog.record_tuples == [
+        ("grr_manage", logging.INFO,
+         "manifest of <one> should be updated; entries to update in manifest "
+         "['data.txt']"),
+    ]
 
 
 def test_repo_dry_run_manifest_needs_update_message(
-        proto_fixture, tmp_path, capsys):
+        proto_fixture, tmp_path, capsys, caplog):
     # Given
     res = proto_fixture.get_resource("one")
     with res.open_raw_file("data.txt", "wt") as outfile:
         outfile.write("alabala2")
 
     # When
-    cli_manage([
-        "repo-manifest", "--dry-run", "-R", str(tmp_path)])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "repo-manifest", "--dry-run", "-R", str(tmp_path)])
 
     # Then
     captured = capsys.readouterr()
-    print(captured.err)
-    assert captured.err == \
-        "manifest of <one> should be updated; " \
-        "entries to update in manifest ['data.txt']\n" \
-        "manifest of <sub/two> is up to date\n" \
-        "manifest of <sub/two(1.0)> is up to date\n" \
-        "manifest of <three(2.0)> is up to date\n" \
-        "manifest of <xxxxx-genome> is up to date\n"
+    assert captured.out == ""
+    assert captured.err == ""
+
+    assert caplog.record_tuples == [
+        ("grr_manage", logging.INFO,
+         "manifest of <one> should be updated; entries to update in manifest "
+         "['data.txt']"),
+        ("grr_manage", logging.INFO,
+         "manifest of <sub/two> is up to date"),
+        ("grr_manage", logging.INFO,
+         "manifest of <sub/two(1.0)> is up to date"),
+        ("grr_manage", logging.INFO,
+         "manifest of <three(2.0)> is up to date"),
+        ("grr_manage", logging.INFO,
+         "manifest of <xxxxx-genome> is up to date"),
+    ]
 
 
 def test_resource_dry_run_manifest_no_update_message(
-        proto_fixture, tmp_path, capsys):
+        proto_fixture, tmp_path, capsys, caplog):
     # Given
 
     # When
-    cli_manage([
-        "resource-manifest", "--dry-run", "-R", str(tmp_path), "-r", "one"])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "resource-manifest", "--dry-run",
+            "-R", str(tmp_path), "-r", "one"])
 
     # Then
     captured = capsys.readouterr()
-    print(captured.err)
-    assert captured.err == \
-        "manifest of <one> is up to date\n"
+    assert captured.out == ""
+    assert captured.err == ""
+
+    assert caplog.record_tuples == [
+        ("grr_manage", logging.INFO, "manifest of <one> is up to date")
+    ]
 
 
 def test_resource_manifest_no_agruments(
-        proto_fixture, tmp_path, mocker, capsys):
+        proto_fixture, tmp_path, mocker, capsys, caplog):
     # Given
     cli_manage([
         "repo-manifest", "-R", str(tmp_path)])
@@ -209,30 +276,41 @@ def test_resource_manifest_no_agruments(
     capsys.readouterr()
 
     # When
-    cli_manage(["resource-manifest"])
+    with caplog.at_level(logging.INFO):
+        cli_manage(["resource-manifest"])
 
     # Then
     out, err = capsys.readouterr()
 
-    assert out.startswith(f"working with repository: {tmp_path}\n")
-    assert err == \
-        "manifest of <one> is up to date\n"
+    assert out == ""
+    assert err == f"working with repository: {tmp_path}\n"
+    assert caplog.record_tuples == [
+        ("grr_manage", logging.INFO, "manifest of <one> is up to date")
+    ]
 
 
 def test_repo_dry_run_manifest_no_update_message(
-        proto_fixture, tmp_path, capsys):
+        proto_fixture, tmp_path, capsys, caplog):
     # Given
 
     # When
-    cli_manage([
-        "repo-manifest", "--dry-run", "-R", str(tmp_path)])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "repo-manifest", "--dry-run", "-R", str(tmp_path)])
 
     # Then
     captured = capsys.readouterr()
-    print(captured.err)
-    assert captured.err == \
-        "manifest of <one> is up to date\n" \
-        "manifest of <sub/two> is up to date\n" \
-        "manifest of <sub/two(1.0)> is up to date\n" \
-        "manifest of <three(2.0)> is up to date\n" \
-        "manifest of <xxxxx-genome> is up to date\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage", logging.INFO,
+         "manifest of <one> is up to date"),
+        ("grr_manage", logging.INFO,
+         "manifest of <sub/two> is up to date"),
+        ("grr_manage", logging.INFO,
+         "manifest of <sub/two(1.0)> is up to date"),
+        ("grr_manage", logging.INFO,
+         "manifest of <three(2.0)> is up to date"),
+        ("grr_manage", logging.INFO,
+         "manifest of <xxxxx-genome> is up to date"),
+    ]
diff --git a/dae/dae/genomic_resources/tests/test_cli_repair.py b/dae/dae/genomic_resources/tests/test_cli_repair.py
index 9e65cc0bb..b866d5f99 100644
--- a/dae/dae/genomic_resources/tests/test_cli_repair.py
+++ b/dae/dae/genomic_resources/tests/test_cli_repair.py
@@ -1,5 +1,6 @@
 # pylint: disable=W0621,C0114,C0116,W0212,W0613
 import os
+import logging
 import textwrap
 
 import pytest
@@ -87,6 +88,9 @@ def test_resource_repair_dry_run(proto_fixture, dask_mocker, tmp_path):
     # Given
     proto_fixture.filesystem.delete(
         os.path.join(proto_fixture.url, ".CONTENTS"))
+
+    proto_fixture.filesystem.delete(str(tmp_path / "one" / ".MANIFEST"))
+
     assert not (tmp_path / "one/histograms").exists()
     assert not (tmp_path / GR_CONTENTS_FILE_NAME).exists()
 
@@ -96,6 +100,7 @@ def test_resource_repair_dry_run(proto_fixture, dask_mocker, tmp_path):
 
     # Then
     assert not (tmp_path / "one/histograms").exists()
+    assert not (tmp_path / "one/.MANIFEST").exists()
 
 
 def test_repo_repair_dry_run(proto_fixture, dask_mocker, tmp_path):
@@ -115,73 +120,106 @@ def test_repo_repair_dry_run(proto_fixture, dask_mocker, tmp_path):
 
 
 def test_resource_repair_need_update_message(
-        proto_fixture, dask_mocker, tmp_path, capsys):
+        proto_fixture, dask_mocker, tmp_path, capsys, caplog):
 
     assert not (tmp_path / "one/histograms").exists()
-    cli_manage([
-        "resource-repair", "--dry-run", "-R", str(tmp_path), "-r", "one"])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "resource-repair", "--dry-run",
+            "-R", str(tmp_path), "-r", "one"])
 
     captured = capsys.readouterr()
-
-    assert captured.err == \
-        "manifest of <one> is up to date\n" \
-        "resource <one> histograms " \
-        "[{'score': 'phastCons100way', 'bins': 100}] need update\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> is up to date"),
+        ("dae.genomic_resources.histogram",
+         logging.INFO,
+         "resource <one> histograms "
+         "[{'score': 'phastCons100way', 'bins': 100}] need "
+         "update"),
+    ]
 
 
 def test_repo_repair_need_update_message(
-        proto_fixture, dask_mocker, tmp_path, capsys):
+        proto_fixture, dask_mocker, tmp_path, capsys, caplog):
 
     assert not (tmp_path / "one/histograms").exists()
-    cli_manage([
-        "repo-repair", "--dry-run", "-R", str(tmp_path)])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "repo-repair", "--dry-run", "-R", str(tmp_path)])
 
     captured = capsys.readouterr()
-
-    assert captured.err == \
-        "manifest of <one> is up to date\n" \
-        "resource <one> histograms " \
-        "[{'score': 'phastCons100way', 'bins': 100}] need update\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> is up to date"),
+        ("dae.genomic_resources.histogram",
+         logging.INFO,
+         "resource <one> histograms "
+         "[{'score': 'phastCons100way', 'bins': 100}] need update"),
+    ]
 
 
 def test_resource_repair_no_update_message(
-        proto_fixture, dask_mocker, tmp_path, capsys):
+        proto_fixture, dask_mocker, tmp_path, capsys, caplog):
     # Given
     cli_manage([
         "resource-repair", "-R", str(tmp_path), "-r", "one"])
     _, _ = capsys.readouterr()
 
     # When
-    cli_manage([
-        "resource-repair", "--dry-run", "-R", str(tmp_path), "-r", "one"])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "resource-repair", "--dry-run",
+            "-R", str(tmp_path), "-r", "one"])
 
     # Then
-    _, err = capsys.readouterr()
-    assert err == \
-        "manifest of <one> is up to date\n" \
-        "histograms of <one> are up to date\n"
+    out, err = capsys.readouterr()
+    assert out == ""
+    assert err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> is up to date"),
+        ("dae.genomic_resources.histogram",
+         logging.INFO,
+         "histograms of <one> are up to date"),
+    ]
 
 
 def test_repo_repair_no_update_message(
-        proto_fixture, dask_mocker, tmp_path, capsys):
+        proto_fixture, dask_mocker, tmp_path, capsys, caplog):
     # Given
     cli_manage([
         "repo-repair", "-R", str(tmp_path)])
     _, _ = capsys.readouterr()
 
     # When
-    cli_manage([
-        "repo-repair", "--dry-run", "-R", str(tmp_path)])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "repo-repair", "--dry-run", "-R", str(tmp_path)])
 
     # Then
-    _, err = capsys.readouterr()
-    assert err == \
-        "manifest of <one> is up to date\n" \
-        "histograms of <one> are up to date\n"
+    out, err = capsys.readouterr()
+    assert out == ""
+    assert err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> is up to date"),
+        ("dae.genomic_resources.histogram",
+         logging.INFO,
+         "histograms of <one> are up to date"),
+    ]
 
 
 def test_resource_repair_dry_run_needs_manifest_update_message(
-        proto_fixture, dask_mocker, tmp_path, capsys):
+        proto_fixture, dask_mocker, tmp_path, capsys, caplog):
     # Given
     cli_manage([
         "resource-repair", "-R", str(tmp_path), "-r", "one"])
@@ -192,20 +230,27 @@ def test_resource_repair_dry_run_needs_manifest_update_message(
     _, _ = capsys.readouterr()
 
     # When
-    cli_manage([
-        "resource-repair", "--dry-run", "-R", str(tmp_path), "-r", "one"])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "resource-repair", "--dry-run", "-R", str(tmp_path), "-r", "one"])
 
     # Then
     captured = capsys.readouterr()
-    print(captured.err)
-    assert captured.err == \
-        "manifest of <one> should be updated; " \
-        "entries to update in manifest ['data.txt']\n" \
-        "histograms of <one> are up to date\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> should be updated; entries to update in manifest "
+         "['data.txt']"),
+        ("grr_manage",
+         logging.INFO,
+         "manfiest one needs update; can't check histograms"),
+    ]
 
 
 def test_repo_repair_dry_run_needs_manifest_update_message(
-        proto_fixture, dask_mocker, tmp_path, capsys):
+        proto_fixture, dask_mocker, tmp_path, capsys, caplog):
     # Given
     cli_manage([
         "repo-repair", "-R", str(tmp_path)])
@@ -215,20 +260,27 @@ def test_repo_repair_dry_run_needs_manifest_update_message(
     _, _ = capsys.readouterr()
 
     # When
-    cli_manage([
-        "repo-repair", "--dry-run", "-R", str(tmp_path)])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "repo-repair", "--dry-run", "-R", str(tmp_path)])
 
     # Then
     captured = capsys.readouterr()
-    print(captured.err)
-    assert captured.err == \
-        "manifest of <one> should be updated; " \
-        "entries to update in manifest ['data.txt']\n" \
-        "histograms of <one> are up to date\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> should be updated; entries to update in manifest "
+         "['data.txt']"),
+        ("grr_manage",
+         logging.INFO,
+         "manfiest one needs update; can't check histograms"),
+    ]
 
 
 def test_resource_repair_dry_run_needs_manifest_and_histogram_update_message(
-        proto_fixture, tabix_file, dask_mocker, tmp_path, capsys):
+        proto_fixture, tabix_file, dask_mocker, tmp_path, capsys, caplog):
     # Given
     cli_manage([
         "resource-repair", "-R", str(tmp_path), "-r", "one"])
@@ -249,36 +301,53 @@ def test_resource_repair_dry_run_needs_manifest_and_histogram_update_message(
     _, _ = capsys.readouterr()
 
     # When
-    cli_manage([
-        "resource-repair", "--dry-run", "-R", str(tmp_path), "-r", "one"])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "resource-repair", "--dry-run", "-R", str(tmp_path), "-r", "one"])
 
     # Then
     captured = capsys.readouterr()
-    assert captured.err == \
-        "manifest of <one> should be updated; entries to update in manifest " \
-        "['data.bgz', 'data.bgz.tbi']\n" \
-        "histograms of <one> are up to date\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> should be updated; entries to update in manifest "
+         "['data.bgz', 'data.bgz.tbi']"),
+        ("grr_manage",
+         logging.INFO,
+         "manfiest one needs update; can't check histograms"),
+    ]
 
     # And after that::
     # Given
     cli_manage([
         "resource-manifest", "-R", str(tmp_path), "-r", "one"])
     _, _ = capsys.readouterr()
+    caplog.clear()
 
     # When
-    cli_manage([
-        "resource-repair", "--dry-run", "-R", str(tmp_path), "-r", "one"])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "resource-repair", "--dry-run", "-R", str(tmp_path), "-r", "one"])
 
     # Then
     captured = capsys.readouterr()
-    assert captured.err == \
-        "manifest of <one> is up to date\n" \
-        "resource <one> histograms " \
-        "[{'score': 'phastCons100way', 'bins': 100}] need update\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> is up to date"),
+        ("dae.genomic_resources.histogram",
+         logging.INFO,
+         "resource <one> histograms "
+         "[{'score': 'phastCons100way', 'bins': 100}] need update"),
+    ]
 
 
 def test_repo_repair_dry_run_needs_manifest_and_histogram_update_message(
-        proto_fixture, tabix_file, dask_mocker, tmp_path, capsys):
+        proto_fixture, tabix_file, dask_mocker, tmp_path, capsys, caplog):
     # Given
     cli_manage([
         "repo-repair", "-R", str(tmp_path)])
@@ -299,29 +368,46 @@ def test_repo_repair_dry_run_needs_manifest_and_histogram_update_message(
     _, _ = capsys.readouterr()
 
     # When
-    cli_manage([
-        "repo-repair", "--dry-run", "-R", str(tmp_path)])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "repo-repair", "--dry-run", "-R", str(tmp_path)])
 
     # Then
     captured = capsys.readouterr()
-    assert captured.err == \
-        "manifest of <one> should be updated; entries to update in manifest " \
-        "['data.bgz', 'data.bgz.tbi']\n" \
-        "histograms of <one> are up to date\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> should be updated; entries to update in manifest "
+         "['data.bgz', 'data.bgz.tbi']"),
+        ("grr_manage",
+         logging.INFO,
+         "manfiest one needs update; can't check histograms"),
+    ]
 
     # And after that::
     # Given
     cli_manage([
         "repo-manifest", "-R", str(tmp_path)])
     _, _ = capsys.readouterr()
+    caplog.clear()
 
     # When
-    cli_manage([
-        "repo-repair", "--dry-run", "-R", str(tmp_path)])
+    with caplog.at_level(logging.INFO):
+        cli_manage([
+            "repo-repair", "--dry-run", "-R", str(tmp_path)])
 
     # Then
     captured = capsys.readouterr()
-    assert captured.err == \
-        "manifest of <one> is up to date\n" \
-        "resource <one> histograms " \
-        "[{'score': 'phastCons100way', 'bins': 100}] need update\n"
+    assert captured.out == ""
+    assert captured.err == ""
+    assert caplog.record_tuples == [
+        ("grr_manage",
+         logging.INFO,
+         "manifest of <one> is up to date"),
+        ("dae.genomic_resources.histogram",
+         logging.INFO,
+         "resource <one> histograms "
+         "[{'score': 'phastCons100way', 'bins': 100}] need update"),
+    ]