Skip to content

Commit

Permalink
feat(git): Add pyff-git to compare git repo revisions
Browse files Browse the repository at this point in the history
  • Loading branch information
petr-muller committed Jul 18, 2018
1 parent 69c5410 commit 08e0471
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 11 deletions.
2 changes: 1 addition & 1 deletion pyff/modules.py
Expand Up @@ -71,7 +71,7 @@ def __str__(self):
return "\n".join(
[
f"Module {hl(module)} changed:\n " + str(change).replace("\n", "\n ")
for module, change in self.changed.items()
for module, change in sorted(self.changed.items())
]
)

Expand Down
51 changes: 51 additions & 0 deletions pyff/repositories.py
@@ -0,0 +1,51 @@
"""This module contains code that handles comparing revisions in Git repository"""

import tempfile
import shutil
import pathlib
from typing import Optional
import git


import pyff.directories as pd
import pyff.packages as pp


class RevisionsPyfference: # pylint: disable=too-few-public-methods
"""Represents a difference between two revisions"""

# RevisionsPyfference is basically the same as DirectoryPyfference, so we
# embed DirectoryPyfference and delegate everything to it
def __init__(self, change: pd.DirectoryPyfference) -> None:
self._change: pd.DirectoryPyfference = change

def __str__(self):
return str(self._change)

@property
def packages(self) -> Optional[pp.PackagesPyfference]:
"""Return what Python packages differ between revisions"""
return self._change.packages


def pyff_git_revision(repository: str, old: str, new: str) -> Optional[RevisionsPyfference]:
"""Compare two revisions in a Git repository"""
with tempfile.TemporaryDirectory() as temporary_wd:
working_directory = pathlib.Path(temporary_wd)
source_dir = working_directory / "source"
old_dir = working_directory / "old"
new_dir = working_directory / "new"

repo = git.Repo.clone_from(repository, source_dir)

repo.git.checkout(old)
shutil.copytree(source_dir, old_dir)

repo.git.checkout(new)
shutil.copytree(source_dir, new_dir)

change = pd.pyff_directory(old_dir, new_dir)
if change:
return RevisionsPyfference(change)

return None
34 changes: 28 additions & 6 deletions pyff/run.py
Expand Up @@ -9,13 +9,13 @@
from pyff.modules import pyff_module_code
from pyff.packages import pyff_package_path
from pyff.directories import pyff_directory
from pyff.repositories import pyff_git_revision
from pyff.kitchensink import highlight, HIGHLIGHTS

LOGGER = logging.getLogger(__name__)


def _pyff_that(function: Callable, what: str) -> None:
parser = ArgumentParser()
def _pyff_that(function: Callable, what: str, parser: ArgumentParser = ArgumentParser()) -> None:
parser.add_argument("old")
parser.add_argument("new")

Expand All @@ -30,7 +30,7 @@ def _pyff_that(function: Callable, what: str) -> None:
)

LOGGER.debug(f"Python Diff: old {what} {args.old} | new {what} {args.new}")
changes = function(pathlib.Path(args.old), pathlib.Path(args.new))
changes = function(pathlib.Path(args.old), pathlib.Path(args.new), args)

if changes is None:
print(
Expand All @@ -45,7 +45,7 @@ def _pyff_that(function: Callable, what: str) -> None:
def pyffmod() -> None:
"""Entry point for the `pyff` command"""

def compare(old, new):
def compare(old, new, _):
"""Open two arguments as files and compare them"""
with open(old, "r") as old_module, open(new, "r") as new_module:
old_version = old_module.read()
Expand All @@ -58,12 +58,34 @@ def compare(old, new):

def pyffpkg() -> None:
"""Entry point for the `pyff-package` command"""
_pyff_that(pyff_package_path, "package")

def compare(old, new, _):
"""Compare two packages"""
return pyff_package_path(old, new)

_pyff_that(compare, "package")


def pyffdir() -> None:
"""Entry point for the `pyff-dir` command"""
_pyff_that(pyff_directory, "directory")

def compare(old, new, _):
"""Compare two directories"""
return pyff_directory(old, new)

_pyff_that(compare, "directory")


def pyffgit() -> None:
"""Entry point for the `pyff-git` command"""
parser = ArgumentParser()
parser.add_argument("repository")

def compare(old, new, args):
"""Compare two revisions in a given Git repo"""
return pyff_git_revision(args.repository, old, new)

_pyff_that(compare, "revision", parser)


if __name__ == "__main__":
Expand Down
1 change: 1 addition & 0 deletions requirements-tests.txt
Expand Up @@ -2,6 +2,7 @@ astroid==2.0.0.dev0
black==18.6b2
colorama==0.3.9
coverage==4.5.1
GitPython==2.1.10
mypy==0.610
pre-commit==1.10.2
pyfakefs==3.4.3
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
@@ -1 +1,2 @@
colorama==0.3.9
GitPython==2.1.10
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -32,6 +32,7 @@
"pyff=pyff.run:pyffmod",
"pyff-package=pyff.run:pyffpkg",
"pyff-dir=pyff.run:pyffdir",
"pyff-git=pyff.run:pyffgit",
]
},
)
8 changes: 4 additions & 4 deletions tests/pyff-git/pyff.clitest
Expand Up @@ -4,10 +4,6 @@
#
# $ pyff-git . 6b1c70980e491235bfc938b6109b9280477e560a f1c3e475ef5dd0d2315d954deef6f815473f5811 --highlight-names quotes
# Package 'pyff' changed:
# Module 'pyfference.py' changed:
# New imported 'Dict' from 'typing'
# New class 'FromImportPyfference' with 0 public methods
# New class 'ModulePyfference' with 0 public methods
# Module 'pyff.py' changed:
# New imported 'Module', 'NodeVisitor' from 'ast'
# New imported 'defaultdict' from new 'collections'
Expand All @@ -16,4 +12,8 @@
# New function '_pyff_from_imports'
# New function '_pyff_modules'
# New function 'pyff_module'
# Module 'pyfference.py' changed:
# New imported 'Dict' from 'typing'
# New class 'FromImportPyfference' with 0 public methods
# New class 'ModulePyfference' with 0 public methods
# $
7 changes: 7 additions & 0 deletions tests/unit/test_classes.py
Expand Up @@ -71,6 +71,13 @@ def test_class_summary(self, classdef):
assert cls.public_methods == {"a", "b", "c"}
assert str(cls) == "class ``Klass'' with 3 public methods"

another_def = self.classdef()
another_def.name = "Llass"
another = pc.ClassSummary(methods=set(), attributes={}, definition=another_def)
assert cls < another
another_def.name = "Jlass"
assert cls > another

def test_attributes(self, classdef):
cls = pc.ClassSummary(
methods={"__init__"}, definition=classdef, attributes={"attrib", "field"}
Expand Down
62 changes: 62 additions & 0 deletions tests/unit/test_repositories.py
@@ -0,0 +1,62 @@
# pylint: disable=missing-docstring, no-self-use, too-few-public-methods

import os
import pathlib
from unittest.mock import MagicMock, patch

import pyff.directories as pd
import pyff.repositories as pr


class TestRevisionsPyfference:
def test_sanity(self):
directory_change = MagicMock(spec=pd.DirectoryPyfference)
directory_change.__str__.return_value = "le change"
change = pr.RevisionsPyfference(change=directory_change)

assert str(change) == "le change"


class TestPyffGitRevision:
@staticmethod
def _make_fake_clone(fs, revisions): # pylint: disable=invalid-name
def _fake_clone_method(_, directory):
fs.create_dir(directory)
fake_repo = MagicMock()

def _fake_checkout(revision):
if revision in revisions:
oldcwd = os.getcwd()
os.chdir(directory)
revisions[revision]()
os.chdir(oldcwd)

fake_repo.git.checkout = _fake_checkout
return fake_repo

return _fake_clone_method

def test_difference(self, fs): # pylint: disable=invalid-name
def checkout_old():
fs.create_file("old_package/__init__.py")

def checkout_new():
fs.create_file("package/__init__.py")

with patch("git.Repo.clone_from") as fake_clone:

fake_clone.side_effect = self._make_fake_clone(
fs, {"old": checkout_old, "new": checkout_new}
)
change = pr.pyff_git_revision("repo", "old", "new")
assert change is not None
assert pathlib.Path("package") in change.packages.new

def test_identical(self, fs): # pylint: disable=invalid-name
def checkout_old():
fs.create_file("package/__init__.py")

with patch("git.Repo.clone_from") as fake_clone:
fake_clone.side_effect = self._make_fake_clone(fs, {"old": checkout_old})
change = pr.pyff_git_revision("repo", "old", "new")
assert change is None

0 comments on commit 08e0471

Please sign in to comment.