Skip to content

Commit

Permalink
Implements --allow-frozen cmd option that enables support for froze…
Browse files Browse the repository at this point in the history
…n revisions.

This new mode will trust `frozen: xxx` comments and use those to check frozen revisions.
If the comment specifies the same revision as the lock file nothing will be changed.
Otherwise the revision is replaced with expected revision tag.
Documents `--allow-frozen` config option in README.

* sync_with_poetry/swp.py

* README.md
  • Loading branch information
real-yfprojects authored and floatingpurr committed Jun 15, 2023
1 parent d431d77 commit 41b6bf2
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 9 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Excerpt from a `.pre-commit-config.yaml` using an example of this hook:
--skip [SKIP ...] Packages to skip
--config CONFIG Path to a custom .pre-commit-config.yaml file
--db PACKAGE_LIST Path to a custom package list (json)
--allow-frozen Trust `frozen: xxx` comments for frozen revisions.
```
Usually this hook uses only dev packages to sync the hooks. Pass `--all`, if you
Expand All @@ -96,6 +97,15 @@ defaults to `.pre-commit-config.yaml`).
Pass `--db <package_list_file>` to point to an alternative package list (json).
Such a file overrides the mapping in [`db.py`](sync_with_poetry/db.py).
Pass `--allow-frozen` if you want to use frozen revisions in your config.
Without this option _SWP_ will replace frozen revisions with the tag name taken
from `poetry.lock` even if the frozen revision specifies the same commit as the
tag. This options relies on `frozen: xxx` comments appended to the line of the
frozen revision where `xxx` will be the tag name corresponding to the commit
hash used. If the comment specifies the same revision as the lock file nothing
is changed. Otherwise the revision is replaced with the expected revision tag
and the `frozen: xxx` comment is removed.
## Supported packages
Supported packages out-of-the-box are listed in
Expand Down
43 changes: 34 additions & 9 deletions sync_with_poetry/swp.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
from sync_with_poetry.db import DEPENDENCY_MAPPING

YAML_FILE = ".pre-commit-config.yaml"
REV_LINE_RE = re.compile(r'^(\s+)rev:(\s*)([\'"]?)([^\s#]+)(.*)(\r?\n)$')
REV_LINE_RE = re.compile(
r'^(\s+)rev:(\s*)(?P<quotes>[\'"]?)(?P<rev>[^\s#]+)(?P=quotes)(\s*)(# frozen: (?P<comment>\S+)\b)?(?P<rest>.*?)(?P<eol>\r?\n)$'
)
FROZEN_REV_RE = re.compile(r"[a-f\d]{40}")


class PoetryItems(object):
Expand All @@ -35,7 +38,6 @@ def __init__(

self._poetry_lock = {}
for package in poetry_list:

# skip
if package["name"] in skip:
continue
Expand Down Expand Up @@ -68,8 +70,8 @@ def sync_repos(
skip: List[str] = [],
config: str = YAML_FILE,
db: Dict[str, Dict[str, str]] = DEPENDENCY_MAPPING,
frozen: bool = False,
) -> int:

retv = 0

toml = TOMLFile(filename)
Expand All @@ -93,21 +95,34 @@ def sync_repos(
idxs = [i for i, line in enumerate(lines) if REV_LINE_RE.match(line)]

for idx, pre_commit_repo in zip(idxs, repo_pattern):

if pre_commit_repo is None:
continue

match = REV_LINE_RE.match(lines[idx])

assert match is not None

if pre_commit_repo["rev"] == match[4].replace('"', "").replace("'", ""):
lock_rev = pre_commit_repo["rev"]
config_rev = match["rev"].replace('"', "").replace("'", "")

if frozen and FROZEN_REV_RE.fullmatch(config_rev) and match["comment"]:
config_rev = match["comment"]

if lock_rev == config_rev:
continue

new_rev_s = yaml.dump({"rev": pre_commit_repo["rev"]}, default_style=match[3])
new_rev_s = yaml.dump({"rev": lock_rev}, default_style=match["quotes"])
new_rev = new_rev_s.split(":", 1)[1].strip()
lines[idx] = f"{match[1]}rev:{match[2]}{new_rev}{match[5]}{match[6]}"
print(f"[{pre_commit_repo['name']}] -> rev: {pre_commit_repo['rev']}")

rest = ""
if match["rest"]:
rest = match[5] or ""
if match["comment"]:
rest += "#"
rest += match["rest"]

lines[idx] = f"{match[1]}rev:{match[2]}{new_rev}{rest}{match['eol']}"

retv |= 1

with open(config, "w", newline="") as f:
Expand All @@ -131,6 +146,14 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
default=YAML_FILE,
help="Path to the .pre-commit-config.yaml file",
)
parser.add_argument(
"--allow-frozen",
action="store_true",
dest="frozen",
help="Trust `frozen: xxx` comments for frozen revisions. "
"If the comment specifies the same revision as the lock file the check passes. "
"Otherwise the revision is replaced with expected revision tag.",
)
parser.add_argument(
"--db",
type=str,
Expand All @@ -144,7 +167,9 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
mapping = json.load(f)
retv = 0
for filename in args.filenames:
retv |= sync_repos(filename, args.skip, args.config, mapping)
retv |= sync_repos(
filename, args.skip, args.config, mapping, frozen=args.frozen
)
return retv


Expand Down
107 changes: 107 additions & 0 deletions tests/test_frozen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import pytest
from py._path.local import LocalPath

from sync_with_poetry import swp

# test cases for frozen revisions
# all these packages have version 1.0.0 in poetry.lock
TEST_REVS = [
" rev: 1.0.0\n",
" rev: 1.0.0 # frozen\n",
" rev: 1.0.0 # frozen: 2.0.0\n",
" rev: 6fd1ced85fc139abd7f5ab4f3d78dab37592cd5e # frozen: 2.0.0\n",
" rev: 6fd1ced85fc139abd7f5ab4f3d78dab37592cd5e # frozen: 1.0.0\n",
" rev: 6fd1ced85fc139abd7f5ab4f3d78dab37592cd5e # frozen\n",
" rev: 6fd1ced85fc139abd7f5ab4f3d78dab37592cd5e # frozen: 1.0.0 fav version\n",
" rev: 6fd1ced85fc139abd7f5ab4f3d78dab37592cd5e # frozen: 2.0.0 fav version\n",
" rev: 6fd1ced85fc139abd7f5ab4f3d78dab37592cd5e # fav version\n",
" rev: 6fd1ced85fc139abd7f5ab4f3d78dab37592cd5e\n",
]


TEST_REVS_UNFROZEN = [
" rev: 1.0.0\n",
" rev: 1.0.0 # frozen\n",
" rev: 1.0.0 # frozen: 2.0.0\n",
" rev: 1.0.0\n",
" rev: 1.0.0\n",
" rev: 1.0.0 # frozen\n",
" rev: 1.0.0 # fav version\n",
" rev: 1.0.0 # fav version\n",
" rev: 1.0.0 # fav version\n",
" rev: 1.0.0\n",
]

TEST_REVS_FROZEN = [
" rev: 1.0.0\n",
" rev: 1.0.0 # frozen\n",
" rev: 1.0.0 # frozen: 2.0.0\n",
" rev: 1.0.0\n",
" rev: 6fd1ced85fc139abd7f5ab4f3d78dab37592cd5e # frozen: 1.0.0\n",
" rev: 1.0.0 # frozen\n",
" rev: 6fd1ced85fc139abd7f5ab4f3d78dab37592cd5e # frozen: 1.0.0 fav version\n",
" rev: 1.0.0 # fav version\n",
" rev: 1.0.0 # fav version\n",
" rev: 1.0.0\n",
]

assert len(TEST_REVS) == len(TEST_REVS_UNFROZEN) == len(TEST_REVS_FROZEN)


def config_content(rev_line: str) -> str:
return (
"repos:\n" " - repo: test\n" + rev_line + " hooks:\n" " - id: test\n"
)


LOCK_CONTENT = (
"[[package]]\n"
'name = "test"\n'
'version = "1.0.0"\n'
'description = "a dummy package"\n'
"optional = false\n"
'python-versions = ">=3.6"\n'
)


DEPENDENCY_MAPPING = {
"test": {
"repo": "test",
"rev": "${rev}",
}
}


def run_and_check(
tmpdir: LocalPath, rev_line: str, expected: str, frozen: bool
) -> None:
lock_file = tmpdir.join("poetry.lock")
lock_file.write(LOCK_CONTENT)
config_file = tmpdir.join(".pre-commit-yaml")
config = config_content(rev_line)
config_file.write(config)

retv = swp.sync_repos(
lock_file.strpath,
frozen=frozen,
db=DEPENDENCY_MAPPING,
config=config_file.strpath,
)

fixed_lines = open(config_file.strpath).readlines()
fixed_rev_line = fixed_lines[2]

assert fixed_rev_line == expected

assert len(config.splitlines()) == len(fixed_lines)
assert retv == int(expected != rev_line)


@pytest.mark.parametrize("rev_line,expected", zip(TEST_REVS, TEST_REVS_UNFROZEN))
def test_frozen_disabled(tmpdir: LocalPath, rev_line: str, expected: str) -> None:
run_and_check(tmpdir, rev_line, expected, frozen=False)


@pytest.mark.parametrize("rev_line,expected", zip(TEST_REVS, TEST_REVS_FROZEN))
def test_frozen_enabled(tmpdir: LocalPath, rev_line: str, expected: str) -> None:
run_and_check(tmpdir, rev_line, expected, frozen=True)

0 comments on commit 41b6bf2

Please sign in to comment.