From 99bbd9d68fd1029c67e8b7d4b8154c35a41252e9 Mon Sep 17 00:00:00 2001
From: Franck Bret <franck.bret@octobus.net>
Date: Fri, 13 Oct 2023 10:49:26 +0200
Subject: [PATCH] Stateful Julia lister

Add a state to the lister to store the ``last_seen_commit`` as a Git
commit hash.

Use Dulwich to retrieve a Git commit walker since
``last_seen_commit`` if any.
For each commit detect if it is a new package or a new package
version commit and returns its origin with commit date as
last_update.
---
 swh/lister/julia/__init__.py                  |  14 +-
 swh/lister/julia/lister.py                    | 121 +++++++++++++---
 .../fake-julia-registry-repository.tar.gz     | Bin 7493 -> 0 bytes
 .../fake-julia-registry-repository_0.tar.gz   | Bin 0 -> 7462 bytes
 .../fake-julia-registry-repository_1.tar.gz   | Bin 0 -> 10961 bytes
 .../data/fake_julia_registry_repository.sh    |  55 +++++++-
 swh/lister/julia/tests/test_lister.py         | 129 ++++++++++++++++--
 7 files changed, 284 insertions(+), 35 deletions(-)
 delete mode 100644 swh/lister/julia/tests/data/fake-julia-registry-repository.tar.gz
 create mode 100644 swh/lister/julia/tests/data/fake-julia-registry-repository_0.tar.gz
 create mode 100644 swh/lister/julia/tests/data/fake-julia-registry-repository_1.tar.gz

diff --git a/swh/lister/julia/__init__.py b/swh/lister/julia/__init__.py
index 8388c0a2..73e5382a 100644
--- a/swh/lister/julia/__init__.py
+++ b/swh/lister/julia/__init__.py
@@ -28,9 +28,9 @@ Origins retrieval strategy
 --------------------------
 
 To build a list of origins we clone the `Julia General registry`_ Git repository, then
-read the `Registry.toml`_ file to get the path to packages directories.
-Each directory have a `Package.toml` file from where we get the Git repository url for
-a package.
+walk through commits with the help of `Dulwich`_ to detect commit related to a new package
+or a new version of a package. For each of those commits we get the path to `Package.toml`
+file from where we get the Git repository url for a package.
 
 Page listing
 ------------
@@ -40,7 +40,12 @@ There is only one page listing all origins url.
 Origins from page
 -----------------
 
-The lister is stateless and yields all origins url from one page.
+The lister yields all origins url from one page.
+
+Each time the lister is executed, the HEAD commit id of `Julia General registry`_
+is stored as ``state.last_seen_commit`` and used on next run to retrieve new origins
+since the last commit.
+
 Each url corresponds to the Git url of the package repository.
 
 Running tests
@@ -71,6 +76,7 @@ You can follow lister execution by displaying logs of swh-lister service::
 .. _JuliaHub: https://juliahub.com/
 .. _Julia Packages: https://julialang.org/packages/
 .. _Registry.toml: https://github.com/JuliaRegistries/General/blob/master/Registry.toml
+.. _Dulwich: https://www.dulwich.io/
 """  # noqa: B950
 
 
diff --git a/swh/lister/julia/lister.py b/swh/lister/julia/lister.py
index 214cbcef..a31c5cc4 100644
--- a/swh/lister/julia/lister.py
+++ b/swh/lister/julia/lister.py
@@ -3,27 +3,40 @@
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
+from dataclasses import asdict, dataclass
+import datetime
 import logging
 from pathlib import Path
 import shutil
 import tempfile
-from typing import Any, Iterator, List, Optional, Tuple
+from typing import Any, Dict, Iterator, Optional
 
 from dulwich import porcelain
+from dulwich.repo import Repo
+from dulwich.walk import WalkEntry
+import iso8601
 import toml
 
 from swh.scheduler.interface import SchedulerInterface
 from swh.scheduler.model import ListedOrigin
 
-from ..pattern import CredentialsType, StatelessLister
+from ..pattern import CredentialsType, Lister
 
 logger = logging.getLogger(__name__)
 
 # Aliasing the page results returned by `get_pages` method from the lister.
-JuliaListerPage = List[Tuple[str, Any]]
+JuliaListerPage = Dict[str, Any]
 
 
-class JuliaLister(StatelessLister[JuliaListerPage]):
+@dataclass
+class JuliaListerState:
+    """Store lister state for incremental mode operations"""
+
+    last_seen_commit: Optional[str] = None
+    """Hash of the latest Git commit when lister was executed"""
+
+
+class JuliaLister(Lister[JuliaListerState, JuliaListerPage]):
     """List Julia packages origins"""
 
     LISTER_NAME = "julia"
@@ -34,7 +47,6 @@ class JuliaLister(StatelessLister[JuliaListerPage]):
         "https://github.com/JuliaRegistries/General.git"  # Julia General Registry
     )
     REPO_PATH = Path(tempfile.mkdtemp(), "General")
-    REGISTRY_PATH = REPO_PATH / "Registry.toml"
 
     def __init__(
         self,
@@ -63,40 +75,111 @@ class JuliaLister(StatelessLister[JuliaListerPage]):
         except FileExistsError:
             porcelain.pull(self.REPO_PATH, remote_location=self.url)
 
+    def state_from_dict(self, d: Dict[str, Any]) -> JuliaListerState:
+        return JuliaListerState(**d)
+
+    def state_to_dict(self, state: JuliaListerState) -> Dict[str, Any]:
+        return asdict(state)
+
+    def get_origin_data(self, entry: WalkEntry) -> Dict[str, Any]:
+        """
+        Given an entry object parse its commit message and other attributes
+        to detect if the commit is valid to describe a new package or
+        a new package version.
+
+        Returns a dict with origin url as key and iso8601 commit date as value
+        """
+        assert entry
+
+        if (
+            entry.commit
+            and entry.changes()
+            and (
+                entry.commit.message.startswith(b"New package: ")
+                or entry.commit.message.startswith(b"New version: ")
+            )
+        ):
+            package_toml = None
+            for change in entry.changes():
+                if change and hasattr(change, "new"):
+                    if change.new.path.endswith(b"/Package.toml"):
+                        package_toml = self.REPO_PATH / change.new.path.decode()
+                        break
+                    elif change.new.path.endswith(b"/Versions.toml"):
+                        versions_path = self.REPO_PATH / change.new.path.decode()
+                        if versions_path.exists():
+                            package_path, _ = change.new.path.decode().split(
+                                "Versions.toml"
+                            )
+                            package_toml = (
+                                self.REPO_PATH / package_path / "Package.toml"
+                            )
+                            break
+
+            if package_toml and package_toml.exists():
+                origin = toml.load(package_toml)["repo"]
+                last_update = datetime.datetime.fromtimestamp(
+                    entry.commit.commit_time,
+                    tz=datetime.timezone.utc,
+                ).isoformat()
+                return {f"{origin}": last_update}
+
+        return {}
+
     def get_pages(self) -> Iterator[JuliaListerPage]:
         """Yield an iterator which returns 'page'
 
-        To build a list of origins the `Julia General registry` Git
-        repository is cloned to get a `Registry.toml` file, an index file of
-        packages directories.
+        To build a list of origins the ``Julia General registry`` Git
+        repository is cloned to look at commits history to discover new
+        package and new package versions.
+
+        Depending on ``last_seen_commit`` state it initiate a commit walker
+        since the last time the lister has been executed.
 
         There is only one page that list all origins urls.
         """
+        # Clone the repository
         self.get_registry_repository()
-        assert self.REGISTRY_PATH.exists()
-        registry = toml.load(self.REGISTRY_PATH)
-        yield registry["packages"].items()
+        assert self.REPO_PATH.exists()
+
+        repo = Repo(str(self.REPO_PATH))
+
+        # Detect commits related to new package and new versions since last_seen_commit
+        if not self.state.last_seen_commit:
+            walker = repo.get_walker()
+        else:
+            last = repo[self.state.last_seen_commit.encode()]
+            walker = repo.get_walker(since=last.commit_time, exclude=[last.id])
+
+        assert walker
+        packages = {}
+        for entry in walker:
+            packages.update(self.get_origin_data(entry=entry))
+
+        yield packages
 
     def get_origins_from_page(self, page: JuliaListerPage) -> Iterator[ListedOrigin]:
         """Iterate on all pages and yield ListedOrigin instances
 
-        Each directory of the Git repository have a `Package.toml` file from
-        where we get the Git repository url for each package.
+        Each directory of the Git repository have a ``Package.toml`` file from
+        where we get the Git repository url as an origin for each package.
         """
         assert self.lister_obj.id is not None
-        assert self.REPO_PATH.exists()
 
-        for uuid, info in page:
-            package_info_path = self.REPO_PATH / info["path"] / "Package.toml"
-            package_info = toml.load(package_info_path)
+        for origin, last_update in page.items():
+            last_update = iso8601.parse_date(last_update)
             yield ListedOrigin(
                 lister_id=self.lister_obj.id,
                 visit_type=self.VISIT_TYPE,
-                url=package_info["repo"],
-                last_update=None,
+                url=origin,
+                last_update=last_update,
             )
 
     def finalize(self) -> None:
+        # Get Git HEAD commit hash
+        repo = Repo(str(self.REPO_PATH))
+        self.state.last_seen_commit = repo.head().decode("ascii")
+        self.updated = True
         # Rm tmp directory REPO_PATH
         if self.REPO_PATH.exists():
             shutil.rmtree(self.REPO_PATH)
diff --git a/swh/lister/julia/tests/data/fake-julia-registry-repository.tar.gz b/swh/lister/julia/tests/data/fake-julia-registry-repository.tar.gz
deleted file mode 100644
index e79d4464de974285e883294c4349226726a91cb0..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 7493
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
I=NK6O2fbD;HUIzs

diff --git a/swh/lister/julia/tests/data/fake-julia-registry-repository_0.tar.gz b/swh/lister/julia/tests/data/fake-julia-registry-repository_0.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..458e4f1670b5ca6d9abff8b1cb7da305e1222783
GIT binary patch
literal 7462
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

literal 0
HcmV?d00001

diff --git a/swh/lister/julia/tests/data/fake-julia-registry-repository_1.tar.gz b/swh/lister/julia/tests/data/fake-julia-registry-repository_1.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..59275dd83e8fa9158179e33bfb777eaf56e17c7a
GIT binary patch
literal 10961
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
bYz0~!c;oo*|KEI69&vljO4tx25E1?lY}!@w

literal 0
HcmV?d00001

diff --git a/swh/lister/julia/tests/data/fake_julia_registry_repository.sh b/swh/lister/julia/tests/data/fake_julia_registry_repository.sh
index 4dfbc222..5721d129 100755
--- a/swh/lister/julia/tests/data/fake_julia_registry_repository.sh
+++ b/swh/lister/julia/tests/data/fake_julia_registry_repository.sh
@@ -27,6 +27,9 @@ some amount of consideration when choosing package names.
 [packages]' > Registry.toml
 
 # Init as a git repository
+# Force author and commit date to be the same
+export GIT_AUTHOR_DATE='2001-01-01T17:18:19+00:00'
+export GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE
 git init
 git add .
 git commit -m "Init fake Julia registry repository for tests purpose"
@@ -50,6 +53,8 @@ git-tree-sha1 = "65301af3ab06b04cf8a52cd43b06222bab5249c2"
 
 echo 'a3ea4736-0a3b-4c29-ac8a-20364318a635 = { name = "Fable", path = "F/Fable" }' >> Registry.toml
 
+export GIT_AUTHOR_DATE='2001-01-02T17:18:19+00:00'
+export GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE
 git add .
 git commit -m "New package: Fable v0.0.2"
 
@@ -132,16 +137,60 @@ git-tree-sha1 = "59619a31c56c9e61b5dabdbd339e30c227c5d13d"
 
 echo 'f1435218-dba5-11e9-1e4d-f1a5fab5fc13 = { name = "Oscar", path = "O/Oscar" }' >> Registry.toml
 
+export GIT_AUTHOR_DATE='2001-01-03T17:18:19+00:00'
+export GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE
 git add .
 git commit -m "New package: Oscar v0.12.1"
 
 # Save some space
 rm .git/hooks/*.sample
 
-# Archive
+# First Archive
 cd ../
-tar -czf fake-julia-registry-repository.tar.gz General
-mv fake-julia-registry-repository.tar.gz ../
+tar -czf fake-julia-registry-repository_0.tar.gz General
+mv fake-julia-registry-repository_0.tar.gz ../
+
+# Add some more commits and build a second archive for incremental tests purpose
+cd General
+echo '
+
+["0.13.0"]
+git-tree-sha1 = "c090495f818a063ed23d2d911fe74cc4358b5351"
+' >> O/Oscar/Versions.toml
+
+# New version, replace previous uuid with a new one
+sed -i -e 's/f1435218-dba5-11e9-1e4d-f1a5fab5fc13/a3ea4736-0a3b-4c29-ac8a-20364318a635/g' Registry.toml
+
+export GIT_AUTHOR_DATE='2001-01-04T17:18:19+00:00'
+export GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE
+git add .
+git commit -m "New version: Oscar v0.13.0"
+
+mkdir -p V/VulkanSpec
+
+touch V/VulkanSpec/Package.toml
+touch V/VulkanSpec/Versions.toml
+
+echo 'name = "VulkanSpec"
+uuid = "99a7788f-8f0f-454f-8f6c-c6cf389551ae"
+repo = "https://github.com/serenity4/VulkanSpec.jl.git"
+' > V/VulkanSpec/Package.toml
+
+echo '["0.1.0"]
+git-tree-sha1 = "b5fef67130191c797007a1484f4dc6bfc840caa2"
+' > V/VulkanSpec/Versions.toml
+
+echo '99a7788f-8f0f-454f-8f6c-c6cf389551ae = { name = "VulkanSpec", path = "V/VulkanSpec" }' >> Registry.toml
+
+export GIT_AUTHOR_DATE='2001-01-05T17:18:19+00:00'
+export GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE
+git add .
+git commit -m "New package: VulkanSpec v0.1.0"
+
+# Second Archive
+cd ../
+tar -czf fake-julia-registry-repository_1.tar.gz General
+mv fake-julia-registry-repository_1.tar.gz ../
 
 # Clean up tmp_dir
 cd ../
diff --git a/swh/lister/julia/tests/test_lister.py b/swh/lister/julia/tests/test_lister.py
index 6e5d2ea9..4febe1ae 100644
--- a/swh/lister/julia/tests/test_lister.py
+++ b/swh/lister/julia/tests/test_lister.py
@@ -5,17 +5,25 @@
 
 from pathlib import Path
 
+from dulwich import porcelain
+import iso8601
+
 from swh.lister.julia.lister import JuliaLister
 from swh.lister.julia.tests import prepare_repository_from_archive
 
-expected_origins = [
-    "https://github.com/leios/Fable.jl.git",
-    "https://github.com/oscar-system/Oscar.jl.git",
-]
+expected_origins_0 = {
+    "https://github.com/leios/Fable.jl.git": "2001-01-02T17:18:19+00:00",
+    "https://github.com/oscar-system/Oscar.jl.git": "2001-01-03T17:18:19+00:00",
+}
+
+expected_origins_1 = {
+    "https://github.com/oscar-system/Oscar.jl.git": "2001-01-04T17:18:19+00:00",
+    "https://github.com/serenity4/VulkanSpec.jl.git": "2001-01-05T17:18:19+00:00",
+}
 
 
 def test_julia_get_registry_repository(datadir, tmp_path, swh_scheduler):
-    archive_path = Path(datadir, "fake-julia-registry-repository.tar.gz")
+    archive_path = Path(datadir, "fake-julia-registry-repository_0.tar.gz")
     repo_url = prepare_repository_from_archive(archive_path, "General", tmp_path)
 
     lister = JuliaLister(url=repo_url, scheduler=swh_scheduler)
@@ -33,17 +41,18 @@ def test_julia_get_registry_repository(datadir, tmp_path, swh_scheduler):
 
 
 def test_julia_lister(datadir, tmp_path, swh_scheduler):
-    archive_path = Path(datadir, "fake-julia-registry-repository.tar.gz")
+    archive_path = Path(datadir, "fake-julia-registry-repository_0.tar.gz")
     repo_url = prepare_repository_from_archive(archive_path, "General", tmp_path)
     lister = JuliaLister(url=repo_url, scheduler=swh_scheduler)
     lister.REPO_PATH = Path(tmp_path, "General")
     lister.REGISTRY_PATH = lister.REPO_PATH / "Registry.toml"
 
     res = lister.run()
-    assert res.origins == 1 + 1
+    assert res.origins == len(expected_origins_0)
 
     scheduler_origins = swh_scheduler.get_listed_origins(lister.lister_obj.id).results
-    assert len(scheduler_origins) == len(expected_origins)
+    assert len(scheduler_origins) == len(expected_origins_0)
+
     assert {
         (
             scheduled.visit_type,
@@ -51,4 +60,106 @@ def test_julia_lister(datadir, tmp_path, swh_scheduler):
             scheduled.last_update,
         )
         for scheduled in scheduler_origins
-    } == {("git", expected, None) for expected in expected_origins}
+    } == {
+        ("git", origin, iso8601.parse_date(last_update))
+        for origin, last_update in expected_origins_0.items()
+    }
+
+
+def test_julia_lister_incremental(datadir, tmp_path, swh_scheduler):
+    archive_path = Path(datadir, "fake-julia-registry-repository_0.tar.gz")
+    repo_url = prepare_repository_from_archive(archive_path, "General", tmp_path)
+
+    # Prepare first run
+    lister = JuliaLister(url=repo_url, scheduler=swh_scheduler)
+    lister.REPO_PATH = Path(tmp_path, "General")
+    lister.REGISTRY_PATH = lister.REPO_PATH / "Registry.toml"
+    # Latest Git commit hash expected
+    with porcelain.open_repo_closing(lister.REPO_PATH) as r:
+        expected_last_seen_commit = r.head().decode("ascii")
+
+    assert expected_last_seen_commit is not None
+    assert lister.state.last_seen_commit is None
+
+    # First run
+    res = lister.run()
+    assert res.pages == 1
+    assert res.origins == len(expected_origins_0)
+    assert lister.state.last_seen_commit == expected_last_seen_commit
+
+    scheduler_origins_0 = swh_scheduler.get_listed_origins(lister.lister_obj.id).results
+    assert len(scheduler_origins_0) == len(expected_origins_0)
+    assert {
+        (
+            scheduled.visit_type,
+            scheduled.url,
+            scheduled.last_update,
+        )
+        for scheduled in scheduler_origins_0
+    } == {
+        ("git", origin, iso8601.parse_date(last_update))
+        for origin, last_update in expected_origins_0.items()
+    }
+
+    # Prepare second run
+    archive_path = Path(datadir, "fake-julia-registry-repository_1.tar.gz")
+    repo_url = prepare_repository_from_archive(archive_path, "General", tmp_path)
+
+    lister = JuliaLister(url=repo_url, scheduler=swh_scheduler)
+    lister.REPO_PATH = Path(tmp_path, "General")
+    lister.REGISTRY_PATH = lister.REPO_PATH / "Registry.toml"
+
+    assert lister.state.last_seen_commit == expected_last_seen_commit
+
+    with porcelain.open_repo_closing(lister.REPO_PATH) as repo:
+        new_expected_last_seen_commit = repo.head().decode("ascii")
+
+    assert expected_last_seen_commit != new_expected_last_seen_commit
+
+    # Second run
+    res = lister.run()
+    assert lister.state.last_seen_commit == new_expected_last_seen_commit
+    assert res.pages == 1
+    # One new package, one new version
+    assert res.origins == len(expected_origins_1)
+
+    scheduler_origins = swh_scheduler.get_listed_origins(lister.lister_obj.id).results
+    expected_origins = {**expected_origins_0, **expected_origins_1}
+    assert len(scheduler_origins) == len(expected_origins)
+
+
+def test_julia_lister_incremental_no_changes(datadir, tmp_path, swh_scheduler):
+    archive_path = Path(datadir, "fake-julia-registry-repository_0.tar.gz")
+    repo_url = prepare_repository_from_archive(archive_path, "General", tmp_path)
+    lister = JuliaLister(url=repo_url, scheduler=swh_scheduler)
+    lister.REPO_PATH = Path(tmp_path, "General")
+    lister.REGISTRY_PATH = lister.REPO_PATH / "Registry.toml"
+
+    # Latest Git commit hash expected
+    with porcelain.open_repo_closing(lister.REPO_PATH) as r:
+        expected_last_seen_commit = r.head().decode("ascii")
+
+    assert expected_last_seen_commit is not None
+    assert lister.state.last_seen_commit is None
+
+    # First run
+    res = lister.run()
+    assert res.pages == 1
+    assert res.origins == len(expected_origins_0)
+    assert expected_last_seen_commit is not None
+    assert lister.state.last_seen_commit == expected_last_seen_commit
+
+    scheduler_origins = swh_scheduler.get_listed_origins(lister.lister_obj.id).results
+    assert len(scheduler_origins) == len(expected_origins_0)
+
+    # Prepare second run, repository state is the same as the one of the first run
+    repo_url = prepare_repository_from_archive(archive_path, "General", tmp_path)
+    lister = JuliaLister(url=repo_url, scheduler=swh_scheduler)
+    assert lister.state.last_seen_commit == expected_last_seen_commit
+
+    # Second run
+    res = lister.run()
+    assert lister.state.last_seen_commit == expected_last_seen_commit
+    assert res.pages == 1
+    # Nothing new
+    assert res.origins == 0
-- 
GitLab