diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index 77b5c9df..af0c5703 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -441,6 +441,9 @@ git_repos = { "pmaports": "https://gitlab.com/postmarketOS/pmaports.git", } +# When a git repository is considered outdated (in seconds) +# (Measuring timestamp of FETCH_HEAD: https://stackoverflow.com/a/9229377) +git_repo_outdated = 3600 * 24 * 2 # # APORTGEN diff --git a/pmb/helpers/git.py b/pmb/helpers/git.py index f2af4192..33d1a0b6 100644 --- a/pmb/helpers/git.py +++ b/pmb/helpers/git.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import logging import os +import time import pmb.build import pmb.chroot.apk @@ -32,22 +33,25 @@ def clone(args, name_repo, shallow=True): if name_repo not in pmb.config.git_repos: raise ValueError("No git repository configured for " + name_repo) - # Skip if already checked out path = get_path(args, name_repo) - if os.path.exists(path): - return + if not os.path.exists(path): + # Build git command + url = pmb.config.git_repos[name_repo] + command = ["git", "clone"] + if shallow: + command += ["--depth=1"] + command += [url, path] - # Build git command - url = pmb.config.git_repos[name_repo] - command = ["git", "clone"] - if shallow: - command += ["--depth=1"] - command += [url, path] + # Create parent dir and clone + logging.info("Clone git repository: " + url) + os.makedirs(args.work + "/cache_git", exist_ok=True) + pmb.helpers.run.user(args, command, output="stdout") - # Create parent dir and clone - logging.info("Clone git repository: " + url) - os.makedirs(args.work + "/cache_git", exist_ok=True) - pmb.helpers.run.user(args, command, output="stdout") + # FETCH_HEAD does not exist after initial clone. Create it, so + # is_outdated() can use it. + fetch_head = path + "/.git/FETCH_HEAD" + if not os.path.exists(fetch_head): + open(fetch_head, "w").close() def rev_parse(args, path, revision="HEAD", extra_args: list = []): @@ -164,3 +168,22 @@ def pull(args, name_repo): command = ["git", "merge", "--ff-only", branch_upstream] pmb.helpers.run.user(args, command, path, "stdout") return 0 + + +def is_outdated(args, path): + # FETCH_HEAD always exists in repositories cloned by pmbootstrap. + # Usually it does not (before first git fetch/pull), but there is no good + # fallback. For exampe, getting the _creation_ date of .git/HEAD is non- + # trivial with python on linux (https://stackoverflow.com/a/39501288). + # Note that we have to assume here, that the user had fetched the "origin" + # repository. If the user fetched another repository, FETCH_HEAD would also + # get updated, even though "origin" may be outdated. For pmbootstrap status + # it is good enough, because it should help the users that are not doing + # much with pmaports.git to know when it is outdated. People who manually + # fetch other repos should usually know that and how to handle that + # situation. + path_head = path + "/.git/FETCH_HEAD" + date_head = os.path.getmtime(path_head) + + date_outdated = time.time() - pmb.config.git_repo_outdated + return date_head <= date_outdated diff --git a/pmb/helpers/status.py b/pmb/helpers/status.py index 6c8b4d80..b72be78a 100644 --- a/pmb/helpers/status.py +++ b/pmb/helpers/status.py @@ -91,6 +91,12 @@ def print_checks_git_repo(args, repo, details=True): "update with 'pmbootstrap pull'") log_ok("up to date with remote branch") + # Outdated remote information + if pmb.helpers.git.is_outdated(args, path): + return log_nok_ret(-5, "outdated remote information", + "update with 'pmbootstrap pull'") + log_ok("remote information updated recently (via git fetch/pull)") + return (0, "") diff --git a/test/test_helpers_git.py b/test/test_helpers_git.py index 17f441fe..45db1ce9 100644 --- a/test/test_helpers_git.py +++ b/test/test_helpers_git.py @@ -4,6 +4,7 @@ import os import sys import pytest import shutil +import time import pmb_test # noqa import pmb_test.git @@ -146,3 +147,29 @@ def test_pull(args, monkeypatch, tmpdir): run_git(["reset", "--hard", "origin/master"]) run_git(["commit", "--allow-empty", "-m", "new"], "remote") assert func(args, name_repo) == 0 + + +def test_is_outdated(args, tmpdir, monkeypatch): + func = pmb.helpers.git.is_outdated + + # Override time.time(): now is "100" + def fake_time(): + return 100.0 + monkeypatch.setattr(time, "time", fake_time) + + # Create .git/FETCH_HEAD + path = str(tmpdir) + os.mkdir(path + "/.git") + fetch_head = path + "/.git/FETCH_HEAD" + open(fetch_head, "w").close() + + # Set mtime to 90 + os.utime(fetch_head, times=(0, 90)) + + # Outdated (date_outdated: 90) + monkeypatch.setattr(pmb.config, "git_repo_outdated", 10) + assert func(args, path) is True + + # Not outdated (date_outdated: 89) + monkeypatch.setattr(pmb.config, "git_repo_outdated", 11) + assert func(args, path) is False diff --git a/test/test_helpers_status.py b/test/test_helpers_status.py index f92bec0a..5fe3fefc 100644 --- a/test/test_helpers_status.py +++ b/test/test_helpers_status.py @@ -83,3 +83,10 @@ def test_print_checks_git_repo(args, monkeypatch, tmpdir): run_git(["pull"]) status, _ = func(args, name_repo) assert status == 0 + + # Outdated remote information + def is_outdated(args, path): + return True + monkeypatch.setattr(pmb.helpers.git, "is_outdated", is_outdated) + status, _ = func(args, name_repo) + assert status == -5