Repository: ishepard/pydriller
Pull request #63 (2019-12-12 10:16:16)

Implements git hyper blame 
Prior, we were using depot_tools to use git hyper blame. With this PR we removed this dependency, and implemented our version of git hyper blame. 

Fix #47 


Patch:
diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index f2e67ed..0000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-version: 1.5.{build}
-
-environment:
-  GIT_DAEMON_PATH: "C:\\Program Files\\Git\\mingw64\\libexec\\git-core"
-
-  matrix:
-    - PYTHON: "C:\\Python35-x64"
-      PYTHON_VERSION: "3.5"
-      GIT_PATH: "%GIT_DAEMON_PATH%"
-    - PYTHON: "C:\\Python36-x64"
-      PYTHON_VERSION: "3.6"
-      GIT_PATH: "%GIT_DAEMON_PATH%"
-    - PYTHON: "C:\\Python37-x64"
-      PYTHON_VERSION: "3.7"
-      GIT_PATH: "%GIT_DAEMON_PATH%"
-
-notifications:
-  - provider: Slack
-    incoming_webhook: 
-      secure: pIuy/qP0oJmXuYm+DKX3oGLNcXuQ376bfiB2sSHFlTsGG0xKxFInQGXN8fwZEyMZWTzrghdZavXgTBtk3caO8/pseTOjeqSIrqiJnZx4Wv8=
-
-  - provider: Email
-    to:
-      - spadini.davide@gmail.com
-    on_build_success: false
-    on_build_failure: true
-    on_build_status_changed: true
-
-install:
-  - set PATH=%PYTHON%;%PYTHON%\Scripts;%GIT_PATH%;%PATH%
-
-  ## Print configuration for debugging.
-  #
-  - |
-      echo %PATH%
-      uname -a
-      git --version
-      where git git-daemon python pip
-      python --version
-      python -c "import struct; print(struct.calcsize('P') * 8)"
-
-  - jar xf test-repos.zip
-  - pip install -r test-requirements.txt
-  - pip install -e .
-
-build: false
-
-test_script:
-  - pytest -s -v tests/
diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml
index 2225300..be75188 100644
--- a/.github/workflows/continuous-integration-workflow.yml
+++ b/.github/workflows/continuous-integration-workflow.yml
@@ -1,5 +1,5 @@
 name: Pydriller workflow
-on: push
+on: [push, pull_request]
 
 jobs:
   build:
@@ -7,7 +7,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macOS-latest, windows-latest]
-        python: [ '3.5.x', '3.6.x', '3.7.x' ]
+        python: [ '3.5.x', '3.6.x', '3.7.x', '3.8.x' ]
     name: Python ${{ matrix.python }} sample on ${{ matrix.os }}
     steps:
       - uses: actions/checkout@master
@@ -37,4 +37,4 @@ jobs:
 
       - name: Publish to codecov
         if: matrix.os == 'ubuntu-latest'
-        run: codecov
\ No newline at end of file
+        run: codecov
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 365000e..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-language: python
-notifications:
-    slack: pydriller:GY8wzJPkOMy22cPAH7umESp9
-
-python:
-  - "3.4"
-  - "3.5"
-  - "3.6"
-  - "3.7"
-
-before_install:
-  - sudo apt-get update
-  - sudo apt-get install -y python
-  - unzip test-repos.zip
-  - git clone https://github.com/apache/hadoop.git test-repos/hadoop
-
-install:
-  - pip install -r test-requirements.txt
-  - pip install codecov pytest-cov
-
-script:
-  - pytest tests/ --cov=pydriller/
-
-after_success:
-  - codecov
diff --git a/docs/gitrepository.rst b/docs/gitrepository.rst
index 902c6aa..3ec3682 100644
--- a/docs/gitrepository.rst
+++ b/docs/gitrepository.rst
@@ -43,7 +43,8 @@ the result is::
 Another very useful API (especially for researchers ;) ) is the one that, given a commit, allows you to retrieve
 all the commits that last "touched" the modified lines of the file (if you pass a bug fixing commit, it will retrieve the bug inducing). 
 
-PS: Since PyDriller 1.9, this function uses (if available) "git hyper-blame" (check `this <https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up>`_ for more info).
+PS: Since PyDriller 1.9, this function can be customized to use "git hyper-blame" (check `this <https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up>`_ for more info).
+Git hyper blame can be instructed to skip specific commits (like commits that refactor the code).
 
 Let's see an example::
 
diff --git a/pydriller/domain/commit.py b/pydriller/domain/commit.py
index 9e1f774..335ebce 100644
--- a/pydriller/domain/commit.py
+++ b/pydriller/domain/commit.py
@@ -135,9 +135,9 @@ class Modification:  # pylint: disable=R0902
 
         :return: str old_path
         """
-        if self._old_path:
+        if self._old_path is not None:
             return str(self._old_path)
-        return self._old_path
+        return None
 
     @property
     def new_path(self):
@@ -146,9 +146,9 @@ class Modification:  # pylint: disable=R0902
 
         :return: str new_path
         """
-        if self._new_path:
+        if self._new_path is not None:
             return str(self._new_path)
-        return self._new_path
+        return None
 
     @property
     def filename(self) -> str:
diff --git a/pydriller/git_repository.py b/pydriller/git_repository.py
index 689b571..7f4393b 100644
--- a/pydriller/git_repository.py
+++ b/pydriller/git_repository.py
@@ -25,6 +25,7 @@ from typing import List, Dict, Tuple, Set, Generator
 from git import Git, Repo, GitCommandError, Commit as GitCommit
 
 from pydriller.domain.commit import Commit, ModificationType, Modification
+from pydriller.utils.hyperblame import GitHyperBlame
 
 logger = logging.getLogger(__name__)
 
@@ -44,6 +45,7 @@ class GitRepository:
         :param str path: path to the repository
         """
         self.path = Path(path)
+        self.hyperblame = GitHyperBlame(path)
         self.project_name = self.path.name
         self.main_branch = None
         self.lock = Lock()
@@ -73,19 +75,6 @@ class GitRepository:
             self._open_repository()
         return self._repo
 
-    @property
-    def hyper_blame_available(self):
-        # Try running 'git hyper-blame' on a file in the repo to check if
-        # the command is available.
-        if self._hyper_blame_available is None:
-            try:
-                self.git.execute(["git", "hyper-blame", "-h"])
-                self._hyper_blame_available = True
-            except GitCommandError as e:
-                logger.debug("Hyper-blame not available. Using normal blame.")
-                self._hyper_blame_available = False
-        return self._hyper_blame_available
-
     def _open_git(self):
         self._git = Git(str(self.path))
 
@@ -179,6 +168,7 @@ class GitRepository:
                 _all.append(os.path.join(path, name))
         return _all
 
+
     def reset(self) -> None:
         """
         Reset the state of the repo, checking out the main branch and
@@ -275,6 +265,7 @@ class GitRepository:
 
     def get_commits_last_modified_lines(self, commit: Commit,
                                         modification: Modification = None,
+                                        hyper_blame: bool = False,
                                         hashes_to_ignore_path: str = None) \
             -> Dict[str, Set[str]]:
         """
@@ -302,18 +293,17 @@ class GitRepository:
 
         :param Commit commit: the commit to analyze
         :param Modification modification: single modification to analyze
+        :param bool hyper_blame: whether to use git hyper blame or the
+            normal blame (by default it uses the normal blame).
         :param str hashes_to_ignore_path: path to a file containing hashes of
-               commits to ignore. Requires "git hyper-blame".
+               commits to ignore. (only works with git hyper blame)
         :return: the set containing all the bug inducing commits
         """
+        hashes_to_ignore = []
         if hashes_to_ignore_path is not None:
-            assert self.hyper_blame_available, "Can't ignore hashes if " \
-                                               "hyper-blame is not " \
-                                               "available. Install it by " \
-                                               "cloning depot_tools and " \
-                                               "adding it to your PATH."
-            hashes_to_ignore_path = os.path.realpath(hashes_to_ignore_path)
-            assert os.path.exists(hashes_to_ignore_path), "The file with the commit hashes to ignore does not exist"
+            assert os.path.exists(hashes_to_ignore_path), \
+                "The file with the commit hashes to ignore does not exist"
+            hashes_to_ignore = open(hashes_to_ignore_path).readlines()
 
         if modification is not None:
             modifications = [modification]
@@ -321,11 +311,13 @@ class GitRepository:
             modifications = commit.modifications
 
         return self._calculate_last_commits(commit, modifications,
-                                            hashes_to_ignore_path)
+                                            hyper_blame,
+                                            hashes_to_ignore)
 
     def _calculate_last_commits(self, commit: Commit,
                                 modifications: List[Modification],
-                                hashes_to_ignore_path: str = None) \
+                                hyper_blame: bool = False,
+                                hashes_to_ignore: List[str] = None) \
             -> Dict[str, Set[str]]:
 
         buggy_commits = {}
@@ -337,8 +329,8 @@ class GitRepository:
                 path = mod.old_path
             deleted_lines = self.parse_diff(mod.diff)['deleted']
             try:
-                blame = self._get_blame(commit.hash, path,
-                                        hashes_to_ignore_path)
+                blame = self._get_blame(commit.hash, path, hyper_blame,
+                                        hashes_to_ignore)
                 for num_line, line in deleted_lines:
                     if not self._useless_line(line.strip()):
                         buggy_commit = blame[num_line - 1].split(' ')[
@@ -357,19 +349,17 @@ class GitRepository:
         return buggy_commits
 
     def _get_blame(self, hash: str, path: str,
-                   hashes_to_ignore_path: str = None):
+                   hyper_blame: bool = False,
+                   hashes_to_ignore: List[str] = None):
         """
         If "git hyper-blame" is available, use it. Otherwise use normal blame.
         """
-        if not self.hyper_blame_available or hashes_to_ignore_path is None:
+        if not hyper_blame or hashes_to_ignore is None:
             return self.git.blame('-w', hash + '^',
                                   '--', path).split('\n')
         else:
-            cmd = ["git", "hyper-blame", hash + '^', path]
-            if hashes_to_ignore_path is not None:
-                cmd.append("--ignore-file={}"
-                           .format(hashes_to_ignore_path))
-            return self.git.execute(cmd).split('\n')
+            return self.hyperblame.hyper_blame(hashes_to_ignore, path,
+                                               hash + '^')
 
     def _useless_line(self, line: str):
         # this covers comments in Java and Python, as well as empty lines.
diff --git a/pydriller/utils/__init__.py b/pydriller/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pydriller/utils/hyperblame.py b/pydriller/utils/hyperblame.py
new file mode 100644
index 0000000..c65974c
--- /dev/null
+++ b/pydriller/utils/hyperblame.py
@@ -0,0 +1,286 @@
+import collections
+from datetime import datetime, timedelta, timezone
+import logging
+
+from git import Git
+
+logger = logging.getLogger(__name__)
+BlameLine = collections.namedtuple(
+    'BlameLine',
+    'commit context lineno_then lineno_now modified')
+
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This is a slightly modified verion of the "git hyper-blame" found in Google
+# depot_tools.
+# See https://github.com/GiantPay/depot_tools/blob/master/git_hyper_blame.py
+# for more information.
+
+class HyperBlameCommit(object):
+    """Info about a commit."""
+    def __init__(self, commithash):
+        self.commithash = commithash
+        self.author = None
+        self.author_mail = None
+        self.author_time = None
+        self.author_tz = None
+        self.committer = None
+        self.committer_mail = None
+        self.committer_time = None
+        self.committer_tz = None
+        self.summary = None
+        self.boundary = None
+        self.previous = None
+        self.filename = None
+
+    def __repr__(self):  # pragma: no cover
+        return '<Commit %s>' % self.commithash
+
+
+class GitHyperBlame:
+    def __init__(self, path: str):
+        self.g = Git(path)
+        self.diff_hunks_cache = {}
+
+    def parse_blame(self, blameoutput):
+        """Parses the output of git blame -p into a data structure."""
+        lines = blameoutput.split('\n')
+        i = 0
+        commits = {}
+
+        while i < len(lines):
+            # Read a commit line and parse it.
+            line = lines[i]
+            i += 1
+            if not line.strip():
+                continue
+            commitline = line.split()
+            commithash = commitline[0]
+            lineno_then = int(commitline[1])
+            lineno_now = int(commitline[2])
+
+            try:
+                commit = commits[commithash]
+            except KeyError:
+                commit = HyperBlameCommit(commithash)
+                commits[commithash] = commit
+
+            # Read commit details until we find a context line.
+            while i < len(lines):
+                line = lines[i]
+                i += 1
+                if line.startswith('\t'):
+                    break
+
+                try:
+                    key, value = line.split(' ', 1)
+                except ValueError:
+                    key = line
+                    value = True
+                setattr(commit, key.replace('-', '_'), value)
+
+            context = line[1:]
+
+            yield BlameLine(commit, context, lineno_then, lineno_now, False)
+
+    def get_parsed_blame(self, filename, revision='HEAD'):
+        blame = self.g.blame('-p', revision, '--', filename)
+        return list(self.parse_blame(blame))
+
+    def hyper_blame(self, ignored, filename, revision='HEAD'):
+        # Map from commit to parsed blame from that commit.
+        blame_from = {}
+
+        def cache_blame_from(filename, commithash):
+            try:
+                return blame_from[commithash]
+            except KeyError:
+                parsed = self.get_parsed_blame(filename, commithash)
+                blame_from[commithash] = parsed
+                return parsed
+
+        parsed = cache_blame_from(filename, revision)
+
+        new_parsed = []
+        show_filenames = False
+        for line in parsed:
+            # If a line references an ignored commit, blame that commit's
+            # parent repeatedly until we find a non-ignored commit.
+            while line.commit.commithash in ignored:
+                if line.commit.previous is None:
+                    # You can't ignore the commit that added this file.
+                    break
+
+                previouscommit, previousfilename = line.commit.previous.split(
+                    ' ', 1)
+                parent_blame = cache_blame_from(previousfilename,
+                                                previouscommit)
+
+                if len(parent_blame) == 0:
+                    # The previous version of this file was empty,
+                    # therefore, you can't ignore this commit.
+                    break
+
+                # line.lineno_then is the line number in question at
+                # line.commit. We need
+                # to translate that line number so that it refers to the
+                # position of the same line on previouscommit.
+                lineno_previous = self.approx_lineno_across_revs(
+                    line.commit.filename, previousfilename,
+                    line.commit.commithash,
+                    previouscommit, line.lineno_then)
+                logger.debug('ignore commit %s on line p%d/t%d/n%d',
+                              line.commit.commithash, lineno_previous,
+                              line.lineno_then,
+                              line.lineno_now)
+
+                # Get the line at lineno_previous in the parent commit.
+                assert 1 <= lineno_previous <= len(parent_blame)
+                newline = parent_blame[lineno_previous - 1]
+
+                # Replace the commit and lineno_then, but not the lineno_now
+                # or context.
+                line = BlameLine(newline.commit, line.context,
+                                 newline.lineno_then,
+                                 line.lineno_now, True)
+                logger.debug('replacing with %r', line)
+
+            # If any line has a different filename to the file's current
+            # name, turn on
+            # filename display for the entire blame output.
+            if line.commit.filename != filename:
+                show_filenames = True
+
+            new_parsed.append(line)
+
+        return self.build_result(new_parsed)
+
+    def approx_lineno_across_revs(self, filename, newfilename, revision,
+                                  newrevision, lineno):
+        """Computes the approximate movement of a line number between two
+        revisions.
+
+        Consider line |lineno| in |filename| at |revision|. This function
+        computes the
+        line number of that line in |newfilename| at |newrevision|. This is
+        necessarily approximate.
+
+        Args:
+          filename: The file (within the repo) at |revision|.
+          newfilename: The name of the same file at |newrevision|.
+          revision: A git revision.
+          newrevision: Another git revision. Note: Can be ahead or behind
+          |revision|.
+          lineno: Line number within |filename| at |revision|.
+
+        Returns:
+          Line number within |newfilename| at |newrevision|.
+        """
+        # This doesn't work that well if there are a lot of line changes
+        # within the
+        # hunk (demonstrated by
+        # GitHyperBlameLineMotionTest.testIntraHunkLineMotion).
+        # A fuzzy heuristic that takes the text of the new line and tries to
+        # find a
+        # deleted line within the hunk that mostly matches the new line
+        # could help.
+
+        # Use the <revision>:<filename> syntax to diff between two blobs.
+        # This is the only way to diff a file that has been renamed.
+        old = '%s:%s' % (revision, filename)
+        new = '%s:%s' % (newrevision, newfilename)
+        hunks = self.cache_diff_hunks(old, new)
+
+        cumulative_offset = 0
+
+        # Find the hunk containing lineno (if any).
+        for (oldstart, oldlength), (newstart, newlength) in hunks:
+            cumulative_offset += newlength - oldlength
+
+            if lineno >= oldstart + oldlength:
+                # Not there yet.
+                continue
+
+            if lineno < oldstart:
+                # Gone too far.
+                break
+
+            # lineno is in [oldstart, oldlength] at revision; [newstart,
+            # newlength] at
+            # newrevision.
+
+            # If newlength == 0, newstart will be the line before the
+            # deleted hunk.
+            # Since the line must have been deleted, just return that as the
+            # nearest
+            # line in the new file. Caution: newstart can be 0 in this case.
+            if newlength == 0:
+                return max(1, newstart)
+
+            newend = newstart + newlength - 1
+
+            # Move lineno based on the amount the entire hunk shifted.
+            lineno = lineno + newstart - oldstart
+            # Constrain the output within the range [newstart, newend].
+            return min(newend, max(newstart, lineno))
+
+        # Wasn't in a hunk. Figure out the line motion based on the
+        # difference in
+        # length between the hunks seen so far.
+        return lineno + cumulative_offset
+
+    def cache_diff_hunks(self, oldrev, newrev):
+        def parse_start_length(s):
+            # Chop the '-' or '+'.
+            s = s[1:]
+            # Length is optional (defaults to 1).
+            try:
+                start, length = s.split(',')
+            except ValueError:
+                start = s
+                length = 1
+            return int(start), int(length)
+
+        try:
+            return self.diff_hunks_cache[(oldrev, newrev)]
+        except KeyError:
+            pass
+
+        # Use -U0 to get the smallest possible hunks.
+        diff = self.g.diff(oldrev, newrev, '-U0')
+
+        # Get all the hunks.
+        hunks = []
+        for line in diff.split('\n'):
+            if not line.startswith('@@'):
+                continue
+            ranges = line.split(' ', 3)[1:3]
+            ranges = tuple(parse_start_length(r) for r in ranges)
+            hunks.append(ranges)
+
+        self.diff_hunks_cache[(oldrev, newrev)] = hunks
+        return hunks
+
+    def build_result(self, parsedblame):
+        table = []
+        for line in parsedblame:
+            offset = line.commit.author_tz
+            hours = int(offset[:-2])
+            minutes = int(offset[-2:])
+            tz = timezone(timedelta(hours=hours, minutes=minutes))
+            author_time = datetime.utcfromtimestamp(int(
+                line.commit.author_time)) + timedelta(hours=hours,
+                                                      minutes=minutes)
+            author_time = author_time.replace(tzinfo=tz)
+            row = ''
+            row = [line.commit.commithash[:8],
+                   '(' + line.commit.author,
+                   author_time.strftime('%Y-%m-%d %H:%M:%S %z'),
+                   str(line.lineno_now) + ('*' if line.modified else '') + ')',
+                   line.context]
+            row.insert(1, line.commit.filename)
+            row = ' '.join(row)
+            table.append(row)
+        return table


Test patch:
diff --git a/tests/test_git_repository.py b/tests/test_git_repository.py
index 6095396..343e4aa 100644
--- a/tests/test_git_repository.py
+++ b/tests/test_git_repository.py
@@ -455,76 +455,37 @@ def test_get_tagged_commits_wo_tags():
     assert len(tagged_commits) == 0
 
 
-@pytest.fixture(scope="session")
-def depot_tools(tmpdir_factory):
+def test_get_commits_last_modified_lines_hyper_blame():
     gr = GitRepository('test-repos/test5/')
-    if not gr.hyper_blame_available:
-        tmpdir = tmpdir_factory.mktemp("depot_tools")
-        Repo.clone_from(url="https://chromium.googlesource.com/chromium/tools/depot_tools.git", to_path=tmpdir)
 
-        with open(os.path.join(str(tmpdir), 'git_hyper_blame.py'), 'r') as f:
-            git_hyper_blame_script = f.read()
-
-        with open(os.path.join(str(tmpdir), 'git_hyper_blame.py'), 'w') as f:
-            f.write(git_hyper_blame_script.replace('#!/usr/bin/env python',
-                                                   '#!/usr/bin/env python2'))
-
-        os.environ["PATH"] += os.pathsep + str(tmpdir)
-
-
-@pytest.mark.skipif(platform.system() == "Windows", reason="depot_tools is not easy to install on Windows CI")
-def test_get_commits_last_modified_lines_hyper_blame(depot_tools):
-    gr = GitRepository('test-repos/test5/')
-
-    buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit('e6d3b38a9ef683e8184eac10a0471075c2808bbd'))
+    buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit(
+        'e6d3b38a9ef683e8184eac10a0471075c2808bbd'), hyper_blame=True)
 
     assert len(buggy_commits) == 1
     assert '540c7f31c18664a38190fafb6721b5174ff4a166' in buggy_commits[
         'B.java']
 
 
-@pytest.mark.skipif(platform.system() == "Windows", reason="depot_tools is not easy to install on Windows CI")
-def test_get_commits_last_modified_lines_hyper_blame_ignore_hash(depot_tools, tmpdir):
-    with open(os.path.join(str(tmpdir), "ignore"), "w") as f:
-        f.write("540c7f31c18664a38190fafb6721b5174ff4a166")
+def test_get_commits_last_modified_lines_hyper_blame_ignore_hash(tmp_path):
+    p = tmp_path / "ignore.txt"
+    p.write_text("540c7f31c18664a38190fafb6721b5174ff4a166")
 
     gr = GitRepository('test-repos/test5/')
 
-    buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit('e6d3b38a9ef683e8184eac10a0471075c2808bbd'), hashes_to_ignore_path=os.path.join(str(tmpdir), "ignore"))
-
-    assert len(buggy_commits) == 1
-    assert '22505e97dca6f843549b3a484b3609be4e3acf17' in buggy_commits[
-        'B.java']
-
-
-@pytest.fixture
-def tmpcwd(tmpdir):
-    cwd = os.getcwd()
-    os.chdir(str(tmpdir))
-    yield cwd
-    os.chdir(cwd)
-
-
-@pytest.mark.skipif(platform.system() == "Windows", reason="depot_tools is not easy to install on Windows CI")
-def test_get_commits_last_modified_lines_hyper_blame_ignore_hash_relative(depot_tools, tmpcwd):
-    with open("ignore", "w") as f:
-        f.write("540c7f31c18664a38190fafb6721b5174ff4a166")
-
-    gr = GitRepository(os.path.join(tmpcwd, 'test-repos/test5/'))
-
-    buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit('e6d3b38a9ef683e8184eac10a0471075c2808bbd'), hashes_to_ignore_path="ignore")
+    buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit(
+        'e6d3b38a9ef683e8184eac10a0471075c2808bbd'), hyper_blame=True,
+        hashes_to_ignore_path=str(p))
 
     assert len(buggy_commits) == 1
     assert '22505e97dca6f843549b3a484b3609be4e3acf17' in buggy_commits[
         'B.java']
 
 
-@pytest.mark.skipif(platform.system() == "Windows", reason="depot_tools is not easy to install on Windows CI")
-def test_get_commits_last_modified_lines_hyper_blame_with_renaming(
-        depot_tools):
+def test_get_commits_last_modified_lines_hyper_blame_with_renaming():
     gr = GitRepository('test-repos/test5/')
 
-    buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit('be0772cbaa2eba32bf97aae885199d1a357ddc93'))
+    buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit(
+        'be0772cbaa2eba32bf97aae885199d1a357ddc93'), hyper_blame=True)
 
     assert len(buggy_commits) == 2
     assert '9568d20856728304ab0b4d2d02fb9e81d0e5156d' in buggy_commits[