Repository: ishepard/pydriller
Pull request #170 (2021-05-10 09:52:11)

Pydriller2
See thread #159 for all the info


Patch:
diff --git a/docs/commit.rst b/docs/commit.rst
index da2f7e9..c69682d 100644
--- a/docs/commit.rst
+++ b/docs/commit.rst
@@ -32,7 +32,7 @@ A Commit object has all the information of a Git commit, and much more. More spe
 
 Example::
 
-    for commit in RepositoryMining('path/to/the/repo').traverse_commits():
+    for commit in Repository('path/to/the/repo').traverse_commits():
         print(
             'The commit {} has been modified by {}, '
             'committed by {} in date {}'.format(
diff --git a/docs/deltamaintainability.rst b/docs/deltamaintainability.rst
index 33a8d11..a961887 100644
--- a/docs/deltamaintainability.rst
+++ b/docs/deltamaintainability.rst
@@ -49,9 +49,9 @@ Example usage
 
 Collecting DMM values from a git repository is  straightforward::
 
-	from pydriller import RepositoryMining
+	from pydriller import Repository
 
-	rm = RepositoryMining("https://github.com/avandeursen/dmm-test-repo")
+	rm = Repository("https://github.com/avandeursen/dmm-test-repo")
 	for commit in rm.traverse_commits():
 		print("| {} | {} | {} | {} |".format(
 			commit.msg,
diff --git a/docs/gitrepository.rst b/docs/git.rst
similarity index 83%
rename from docs/gitrepository.rst
rename to docs/git.rst
index 3066860..db5c05d 100644
--- a/docs/gitrepository.rst
+++ b/docs/git.rst
@@ -1,22 +1,22 @@
-.. _gitrepository_toplevel:
+.. _git_toplevel:
 
-==============
-GitRepository
-==============
+====
+Git
+====
 
-GitRepository is a wrapper for the most common utilities of Git, such as checkout and reset.
+Git is a wrapper for the most common utilities of Git, such as checkout and reset.
 For example, to checkout a specific commit or branch::
 
-    gr = GitRepository('test-repos/git-1/')
+    gr = Git('test-repos/git-1/')
     gr.checkout('a7053a4dcd627f5f4f213dc9aa002eb1caf926f8')
 
 However, **be careful!** Git checkout changes the state of the repository on the hard
 disk, hence you should not use this command if other processes (maybe threads? or multiple 
 repository mining?) read from the same repository.
 
-Moreover, GitRepository can be used to obtain different information from the repository::
+Moreover, Git can be used to obtain different information from the repository::
 
-    gr = GitRepository('test-repos/test1')
+    gr = Git('test-repos/test1')
     gr.get_list_commits()                  # get the list of all commits
     gr.get_commit('cc5b002')               # get the specific commit
     gr.files()                             # get the list of files present in the repo at the current commit
@@ -36,7 +36,7 @@ Let's see an example::
     # commit ghi modified line 3 of file A
     # commit lmn deleted lines 1 and 2 of file A
     
-    gr = GitRepository('test-repos/test5')
+    gr = Git('test-repos/test5')
     
     commit = gr.get_commit('lmn')
     buggy_commits = gr.get_commits_last_modified_lines(commit)
diff --git a/docs/index.rst b/docs/index.rst
index ee7dbac..f3af32a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -11,10 +11,10 @@ PyDriller documentation!
    
    intro
    tutorial
-   repositorymining
+   repository
    commit
-   modifications
-   gitrepository
+   modifiedfile
+   git
    deltamaintainability
    processmetrics
    reference
diff --git a/docs/modifications.rst b/docs/modifiedfile.rst
similarity index 95%
rename from docs/modifications.rst
rename to docs/modifiedfile.rst
index c9484c9..e5657ac 100644
--- a/docs/modifications.rst
+++ b/docs/modifiedfile.rst
@@ -1,7 +1,7 @@
-.. _modifications_toplevel:
+.. _modifiedfile_toplevel:
 
 =============
-Modifications
+ModifiedFile
 =============
 
 You can get the list of modified files, as well as their diffs and current source code. To that, all you have to do is to get the list of *Modifications* that exists inside Commit. A modification object has the following fields:
@@ -28,7 +28,7 @@ You can get the list of modified files, as well as their diffs and current sourc
 
 For example::
 
-    for commit in RepositoryMining('path/to/the/repo').traverse_commits():
+    for commit in Repository('path/to/the/repo').traverse_commits():
         for m in commit.modifications:
             print(
                 "Author {}".format(commit.author.name),
diff --git a/docs/reference.rst b/docs/reference.rst
index e7f8c37..fb84b80 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -11,19 +11,19 @@ Version
    Current Pydriller version.
 
 
-GitRepository
+Git
 -------------
 
-.. automodule:: pydriller.git_repository
+.. automodule:: pydriller.git
    :members:
    :undoc-members:
    :special-members:
    :exclude-members: __dict__, __weakref__, __eq__, __str__, __hash__
 
-RepositoryMining
+Repository
 ----------------
 
-.. automodule:: pydriller.repository_mining
+.. automodule:: pydriller.repository
    :members:
    :undoc-members:
    :special-members:
diff --git a/docs/repositorymining.rst b/docs/repository.rst
similarity index 74%
rename from docs/repositorymining.rst
rename to docs/repository.rst
index 089726b..a5fcf1d 100644
--- a/docs/repositorymining.rst
+++ b/docs/repository.rst
@@ -1,28 +1,28 @@
-.. _repositorymining_toplevel:
+.. _repository_toplevel:
 
-=================
-RepositoryMining
-=================
-`RepositoryMining` is the main class of Pydriller, responsible of returning the list of commits you want.
-One of the main advantage of using PyDriller to mine software repositories is that it is highly configurable. We will now see all the options that once can pass to RepositoryMining.
+============
+Repository
+============
+`Repository` is the main class of Pydriller, responsible of returning the list of commits you want.
+One of the main advantage of using PyDriller to mine software repositories is that it is highly configurable. We will now see all the options that once can pass to Repository.
 
 Simple Scenario
 ===============
 This is the "Hello World" of Pydriller::
 
-    for commit in RepositoryMining("/Users/dspadini/myrepo").traverse_commits():
+    for commit in Repository("/Users/dspadini/myrepo").traverse_commits():
         print(commit.hash)
 
-The function `traverse_commits()` of `RepositoryMining` will return the selected commits, in this simple case *all of them*.
-Now let's see how we can customize `RepositoryMining`.
+The function `traverse_commits()` of `Repository` will return the selected commits, in this simple case *all of them*.
+Now let's see how we can customize `Repository`.
 
 Selecting projects to analyze
 =============================
-The only required parameter of `RepositoryMining` is **path_to_repo**, which specifies the repo(s) to analyze. It must be of type `str` or `List[str]`, meaning analyze only one repository or more than one.
+The only required parameter of `Repository` is **path_to_repo**, which specifies the repo(s) to analyze. It must be of type `str` or `List[str]`, meaning analyze only one repository or more than one.
 
 Furthermore, PyDriller supports both local and remote repositories: if you pass an URL, PyDriller will automatically create a temporary folder, clone the repository, run the study, and finally delete the temporary folder. 
 
-For example, the following are all possible inputs for `RepositoryMining`::
+For example, the following are all possible inputs for `Repository`::
     
     # analyze only 1 local repository
     url = "repos/pydriller/" 
@@ -41,7 +41,7 @@ To keep track of what project PyDriller is analyzing, the `Commit` object has a
 Selecting the Commit Range
 ==========================
 
-By default, PyDriller analyzes all the commits in the repository. However, filters can be applied to `RepositoryMining` to visit *only specific* commits.
+By default, PyDriller analyzes all the commits in the repository. However, filters can be applied to `Repository` to visit *only specific* commits.
 
 * **single** *(str)*: single hash of the commit. The visitor will be called only on this commit
 
@@ -66,27 +66,27 @@ By default, PyDriller analyzes all the commits in the repository. However, filte
 Examples::
 
     # Analyze single commit
-    RepositoryMining('path/to/the/repo', single='6411e3096dd2070438a17b225f44475136e54e3a').traverse_commits()
+    Repository('path/to/the/repo', single='6411e3096dd2070438a17b225f44475136e54e3a').traverse_commits()
 
     # Since 8/10/2016
-    RepositoryMining('path/to/the/repo', since=datetime(2016, 10, 8, 17, 0, 0)).traverse_commits()
+    Repository('path/to/the/repo', since=datetime(2016, 10, 8, 17, 0, 0)).traverse_commits()
 
     # Between 2 dates
     dt1 = datetime(2016, 10, 8, 17, 0, 0)
     dt2 = datetime(2016, 10, 8, 17, 59, 0)
-    RepositoryMining('path/to/the/repo', since=dt1, to=dt2).traverse_commits()
+    Repository('path/to/the/repo', since=dt1, to=dt2).traverse_commits()
 
     # Between tags
     from_tag = 'tag1'
     to_tag = 'tag2'
-    RepositoryMining('path/to/the/repo', from_tag=from_tag, to_tag=to_tag).traverse_commits()
+    Repository('path/to/the/repo', from_tag=from_tag, to_tag=to_tag).traverse_commits()
 
     # Up to a date
     dt1 = datetime(2016, 10, 8, 17, 0, 0, tzinfo=to_zone)
-    RepositoryMining('path/to/the/repo', to=dt1).traverse_commits()
+    Repository('path/to/the/repo', to=dt1).traverse_commits()
 
     # !!!!! ERROR !!!!! THIS IS NOT POSSIBLE
-    RepositoryMining('path/to/the/repo', from_tag=from_tag, from_commit=from_commit).traverse_commits()
+    Repository('path/to/the/repo', from_tag=from_tag, from_commit=from_commit).traverse_commits()
 
 **IMPORTANT**: it is **not** possible to configure more than one filter of the same category (for example, more than one *from*). It is also **not** possible to have the *single* filter together with other filters!
 
@@ -107,22 +107,22 @@ PyDriller comes with a set of common commit filters that you can apply:
 Examples::
 
     # Only commits in branch1
-    RepositoryMining('path/to/the/repo', only_in_branch='branch1').traverse_commits()
+    Repository('path/to/the/repo', only_in_branch='branch1').traverse_commits()
 
     # Only commits in branch1 and no merges
-    RepositoryMining('path/to/the/repo', only_in_branch='branch1', only_no_merge=True).traverse_commits()
+    Repository('path/to/the/repo', only_in_branch='branch1', only_no_merge=True).traverse_commits()
 
     # Only commits of author "ishepard" (yeah, that's me)
-    RepositoryMining('path/to/the/repo', only_authors=['ishepard']).traverse_commits()
+    Repository('path/to/the/repo', only_authors=['ishepard']).traverse_commits()
 
     # Only these 3 commits
-    RepositoryMining('path/to/the/repo', only_commits=['hash1', 'hash2', 'hash3']).traverse_commits()
+    Repository('path/to/the/repo', only_commits=['hash1', 'hash2', 'hash3']).traverse_commits()
 
     # Only commit that modified "Matricula.javax" 
-    RepositoryMining('path/to/the/repo', filepath='Matricula.javax').traverse_commits()
+    Repository('path/to/the/repo', filepath='Matricula.javax').traverse_commits()
 
     # Only commits that modified a java file
-    RepositoryMining('path/to/the/repo', only_modifications_with_file_types=['.java']).traverse_commits()
+    Repository('path/to/the/repo', only_modifications_with_file_types=['.java']).traverse_commits()
 
 
 Other Configurations
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 3c5c5bf..1e4ef76 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -6,27 +6,27 @@
 Getting Started
 ==================
 
-Using PyDriller is very simple. You only need to create `RepositoryMining`: this class will receive in input the path to the repository and will return a generator that iterates over the commits. For example::
+Using PyDriller is very simple. You only need to create `Repository`: this class will receive in input the path to the repository and will return a generator that iterates over the commits. For example::
 
-    for commit in RepositoryMining('path/to/the/repo').traverse_commits():
+    for commit in Repository('path/to/the/repo').traverse_commits():
         print('Hash {}, author {}'.format(commit.hash, commit.author.name))
 
 will print the name of the developers for each commit. 
 
-Inside `RepositoryMining`, you will have to configure which projects to analyze, for which commits, for which dates etc. For all the possible
-configurations, have a look at :ref:`repositorymining_toplevel`.
+Inside `Repository`, you will have to configure which projects to analyze, for which commits, for which dates etc. For all the possible
+configurations, have a look at :ref:`repository_toplevel`.
 
 We can also pass a list of repositories (both local and remote), and PyDriller will analyze sequentially. In case of a remote repository, PyDriller will clone it in a temporary folder, and delete it afterwards. For example::
 
     urls = ["repos/repo1", "repos/repo2", "https://github.com/ishepard/pydriller.git", "repos/repo3", "https://github.com/apache/hadoop.git"]
-    for commit in RepositoryMining(path_to_repo=urls).traverse_commits():
+    for commit in Repository(path_to_repo=urls).traverse_commits():
         print("Project {}, commit {}, date {}".format(
                commit.project_path, commit.hash, commit.author_date))
 
 
 Let's make another example: print all the modified files for every commit. This does the magic::
 
-    for commit in RepositoryMining('path/to/the/repo').traverse_commits():
+    for commit in Repository('path/to/the/repo').traverse_commits():
         for modification in commit.modifications:
             print('Author {} modified {} in commit {}'.format(commit.author.name, modification.filename, commit.hash))
 
@@ -36,7 +36,7 @@ Behind the scenes, PyDriller opens the Git repository and extracts all the neces
 
 Furthermore, PyDriller can calculate structural metrics of every file changed in a commit. To calculate these metrics, Pydriller relies on `Lizard <https://github.com/terryyin/lizard>`_, a powerful tool that can analyze source code of many different programming languages, both at class and method level! ::
 
-    for commit in RepositoryMining('path/to/the/repo').traverse_commits():
+    for commit in Repository('path/to/the/repo').traverse_commits():
         for mod in commit.modifications:
             print('{} has complexity of {}, and it contains {} methods'.format(
                   mod.filename, mod.complexity, len(mod.methods)))
diff --git a/pydriller/__init__.py b/pydriller/__init__.py
index f0cc822..d8778dd 100644
--- a/pydriller/__init__.py
+++ b/pydriller/__init__.py
@@ -1,5 +1,5 @@
 # pylint: disable=C0111
-from .domain.commit import Commit, Modification, ModificationType # noqa
-from .repository_mining import RepositoryMining, GitRepository    # noqa
+from .domain.commit import Commit, ModifiedFile, ModificationType # noqa
+from .repository import Repository, Git    # noqa
 
 __version__ = "1.15.5"
diff --git a/pydriller/domain/commit.py b/pydriller/domain/commit.py
index e833784..bd7e223 100644
--- a/pydriller/domain/commit.py
+++ b/pydriller/domain/commit.py
@@ -23,6 +23,8 @@ from enum import Enum
 from pathlib import Path
 from typing import List, Set, Dict, Tuple, Optional
 
+import hashlib
+
 import lizard
 import lizard_languages
 from git import Diff, Git, Commit as GitCommit, NULL_TREE
@@ -49,6 +51,7 @@ class DMMProperty(Enum):
     """
     Maintainability properties of the Delta Maintainability Model.
     """
+
     UNIT_SIZE = 1
     UNIT_COMPLEXITY = 2
     UNIT_INTERFACING = 3
@@ -89,9 +92,16 @@ class Method:
     def __hash__(self):
         # parameters are used in hashing in order to
         # prevent collisions when overloading method names
-        return hash(('name', self.name,
-                     'long_name', self.long_name,
-                     'params', (x for x in self.parameters)))
+        return hash(
+            (
+                "name",
+                self.name,
+                "long_name",
+                self.long_name,
+                "params",
+                (x for x in self.parameters),
+            )
+        )
 
     UNIT_SIZE_LOW_RISK_THRESHOLD = 15
     """
@@ -130,28 +140,34 @@ class Method:
         if dmm_prop is DMMProperty.UNIT_COMPLEXITY:
             return self.complexity <= Method.UNIT_COMPLEXITY_LOW_RISK_THRESHOLD
         assert dmm_prop is DMMProperty.UNIT_INTERFACING
-        return len(self.parameters) <= Method.UNIT_INTERFACING_LOW_RISK_THRESHOLD
+        return (
+            len(self.parameters) <= Method.UNIT_INTERFACING_LOW_RISK_THRESHOLD
+        )
 
 
-class Modification:
+class ModifiedFile:
     """
     This class contains information regarding a modified file in a commit.
     """
 
-    def __init__(self, old_path: Optional[str], new_path: Optional[str],
-                 change_type: ModificationType,
-                 diff_and_sc: Dict[str, str]):
+    def __init__(
+        self,
+        old_path: Optional[str],
+        new_path: Optional[str],
+        change_type: ModificationType,
+        diff_and_sc: Dict[str, str],
+    ):
         """
-        Initialize a modification. A modification carries on information
+        Initialize a modified file. A modified file carries on information
         regarding the changed file. Normally, you shouldn't initialize a new
         one.
         """
         self._old_path = Path(old_path) if old_path is not None else None
         self._new_path = Path(new_path) if new_path is not None else None
         self.change_type = change_type
-        self.diff = diff_and_sc['diff']
-        self.source_code = diff_and_sc['source_code']
-        self.source_code_before = diff_and_sc['source_code_before']
+        self.diff = diff_and_sc["diff"]
+        self.source_code = diff_and_sc["source_code"]
+        self.source_code_before = diff_and_sc["source_code_before"]
 
         self._nloc = None
         self._complexity = None
@@ -159,31 +175,43 @@ class Modification:
         self._function_list = []  # type: List[Method]
         self._function_list_before = []  # type: List[Method]
 
+    def __hash__(self):
+        """
+        Implements hashing similar as Git would do it. Alternatively, if the
+        object had the hash of th Git Blob, one could use that directly.
+
+        :return: int hash
+        """
+        string = " ".join(
+            [self.change_type.name, self.new_path, self.source_code]
+        )
+        return hash(hashlib.sha256(string.encode("utf-8")).hexdigest())
+
     @property
-    def added(self) -> int:
+    def added_lines(self) -> int:
         """
         Return the total number of added lines in the file.
 
         :return: int lines_added
         """
-        added = 0
-        for line in self.diff.replace('\r', '').split("\n"):
-            if line.startswith('+') and not line.startswith('+++'):
-                added += 1
-        return added
+        added_lines = 0
+        for line in self.diff.replace("\r", "").split("\n"):
+            if line.startswith("+") and not line.startswith("+++"):
+                added_lines += 1
+        return added_lines
 
     @property
-    def removed(self):
+    def deleted_lines(self):
         """
         Return the total number of deleted lines in the file.
 
         :return: int lines_deleted
         """
-        removed = 0
-        for line in self.diff.replace('\r', '').split("\n"):
-            if line.startswith('-') and not line.startswith('---'):
-                removed += 1
-        return removed
+        deleted_lines = 0
+        for line in self.diff.replace("\r", "").split("\n"):
+            if line.startswith("-") and not line.startswith("---"):
+                deleted_lines += 1
+        return deleted_lines
 
     @property
     def old_path(self):
@@ -276,8 +304,11 @@ class Modification:
 
         :return: Dictionary
         """
-        lines = self.diff.split('\n')
-        modified_lines = {'added': [], 'deleted': []}  # type: Dict[str, List[Tuple[int, str]]]
+        lines = self.diff.split("\n")
+        modified_lines = {
+            "added": [],
+            "deleted": [],
+        }  # type: Dict[str, List[Tuple[int, str]]]
 
         count_deletions = 0
         count_additions = 0
@@ -287,18 +318,18 @@ class Modification:
             count_deletions += 1
             count_additions += 1
 
-            if line.startswith('@@'):
+            if line.startswith("@@"):
                 count_deletions, count_additions = self._get_line_numbers(line)
 
-            if line.startswith('-'):
-                modified_lines['deleted'].append((count_deletions, line[1:]))
+            if line.startswith("-"):
+                modified_lines["deleted"].append((count_deletions, line[1:]))
                 count_additions -= 1
 
-            if line.startswith('+'):
-                modified_lines['added'].append((count_additions, line[1:]))
+            if line.startswith("+"):
+                modified_lines["added"].append((count_additions, line[1:]))
                 count_deletions -= 1
 
-            if line == r'\ No newline at end of file':
+            if line == r"\ No newline at end of file":
                 count_deletions -= 1
                 count_additions -= 1
 
@@ -309,7 +340,9 @@ class Modification:
         token = line.split(" ")
         numbers_old_file = token[1]
         numbers_new_file = token[2]
-        delete_line_number = int(numbers_old_file.split(",")[0].replace("-", "")) - 1
+        delete_line_number = (
+            int(numbers_old_file.split(",")[0].replace("-", "")) - 1
+        )
         additions_line_number = int(numbers_new_file.split(",")[0]) - 1
         return delete_line_number, additions_line_number
 
@@ -348,18 +381,28 @@ class Modification:
         """
         new_methods = self.methods
         old_methods = self.methods_before
-        added = self.diff_parsed['added']
-        deleted = self.diff_parsed['deleted']
-
-        methods_changed_new = {y for x in added for y in new_methods if
-                               y.start_line <= x[0] <= y.end_line}
-        methods_changed_old = {y for x in deleted for y in old_methods if
-                               y.start_line <= x[0] <= y.end_line}
+        added = self.diff_parsed["added"]
+        deleted = self.diff_parsed["deleted"]
+
+        methods_changed_new = {
+            y
+            for x in added
+            for y in new_methods
+            if y.start_line <= x[0] <= y.end_line
+        }
+        methods_changed_old = {
+            y
+            for x in deleted
+            for y in old_methods
+            if y.start_line <= x[0] <= y.end_line
+        }
 
         return list(methods_changed_new.union(methods_changed_old))
 
     @staticmethod
-    def _risk_profile(methods: List[Method], dmm_prop: DMMProperty) -> Tuple[int, int]:
+    def _risk_profile(
+        methods: List[Method], dmm_prop: DMMProperty
+    ) -> Tuple[int, int]:
         """
         Return the risk profile of the set of methods, with two bins: risky, or non risky.
         The risk profile is a pair (v_low, v_high), where
@@ -384,7 +427,9 @@ class Modification:
         :return: total delta risk profile for this property.
         """
         assert self.language_supported
-        low_before, high_before = self._risk_profile(self.methods_before, dmm_prop)
+        low_before, high_before = self._risk_profile(
+            self.methods_before, dmm_prop
+        )
         low_after, high_after = self._risk_profile(self.methods, dmm_prop)
         return low_after - low_before, high_after - high_before
 
@@ -397,8 +442,9 @@ class Modification:
             return
 
         if self.source_code and self._nloc is None:
-            analysis = lizard.analyze_file.analyze_source_code(self.filename,
-                                                               self.source_code)
+            analysis = lizard.analyze_file.analyze_source_code(
+                self.filename, self.source_code
+            )
             self._nloc = analysis.nloc
             self._complexity = analysis.CCN
             self._token_count = analysis.token_count
@@ -406,16 +452,19 @@ class Modification:
             for func in analysis.function_list:
                 self._function_list.append(Method(func))
 
-        if include_before and self.source_code_before and \
-                not self._function_list_before:
+        if (
+            include_before
+            and self.source_code_before
+            and not self._function_list_before
+        ):
             anal = lizard.analyze_file.analyze_source_code(
-                self.filename, self.source_code_before)
+                self.filename, self.source_code_before
+            )
 
-            self._function_list_before = [
-                Method(x) for x in anal.function_list]
+            self._function_list_before = [Method(x) for x in anal.function_list]
 
     def __eq__(self, other):
-        if not isinstance(other, Modification):
+        if not isinstance(other, ModifiedFile):
             return NotImplemented
         if self is other:
             return True
@@ -441,6 +490,19 @@ class Commit:
         self._branches = None
         self._conf = conf
 
+    def __hash__(self):
+        """
+        Since already used in Git for identification use the SHA of the commit
+        as hash value.
+
+        :return: int hash
+        """
+        # Unfortunately, the Git hash cannot be used for the Python object
+        # directly. The documentation says it "should" return an integer
+        # https://docs.python.org/3/reference/datamodel.html#object.__hash__
+        # but I just learned it **has** to return one.
+        return hash(self._c_object.hexsha)
+
     @property
     def hash(self) -> str:
         """
@@ -457,8 +519,9 @@ class Commit:
 
         :return: author
         """
-        return Developer(self._c_object.author.name,
-                         self._c_object.author.email)
+        return Developer(
+            self._c_object.author.name, self._c_object.author.email
+        )
 
     @property
     def committer(self) -> Developer:
@@ -467,8 +530,9 @@ class Commit:
 
         :return: committer
         """
-        return Developer(self._c_object.committer.name,
-                         self._c_object.committer.email)
+        return Developer(
+            self._c_object.committer.name, self._c_object.committer.email
+        )
 
     @property
     def project_name(self) -> str:
@@ -477,7 +541,7 @@ class Commit:
 
         :return: project name
         """
-        return Path(self._conf.get('path_to_repo')).name
+        return Path(self._conf.get("path_to_repo")).name
 
     @property
     def project_path(self) -> str:
@@ -486,7 +550,7 @@ class Commit:
 
         :return: project path
         """
-        return str(Path(self._conf.get('path_to_repo')))
+        return str(Path(self._conf.get("path_to_repo")))
 
     @property
     def author_date(self) -> datetime:
@@ -591,7 +655,7 @@ class Commit:
         return self._c_object.stats.total["files"]
 
     @property
-    def modifications(self) -> List[Modification]:
+    def modified_files(self) -> List[ModifiedFile]:
         """
         Return a list of modified files. The list is empty if the commit is
         a merge commit. For more info on this, see
@@ -608,17 +672,17 @@ class Commit:
 
     def _get_modifications(self):
         options = {}
-        if self._conf.get('histogram'):
-            options['histogram'] = True
+        if self._conf.get("histogram"):
+            options["histogram"] = True
 
-        if self._conf.get('skip_whitespaces'):
-            options['w'] = True
+        if self._conf.get("skip_whitespaces"):
+            options["w"] = True
 
         if len(self.parents) == 1:
             # the commit has a parent
-            diff_index = self._c_object.parents[0].diff(self._c_object,
-                                                        create_patch=True,
-                                                        **options)
+            diff_index = self._c_object.parents[0].diff(
+                self._c_object, create_patch=True, **options
+            )
         elif len(self.parents) > 1:
             # if it's a merge commit, the modified files of the commit are the
             # conflicts. This because if the file is not in conflict,
@@ -634,13 +698,13 @@ class Commit:
         else:
             # this is the first commit of the repo. Comparing it with git
             # NULL TREE
-            diff_index = self._c_object.diff(NULL_TREE,
-                                             create_patch=True,
-                                             **options)
+            diff_index = self._c_object.diff(
+                NULL_TREE, create_patch=True, **options
+            )
 
         return self._parse_diff(diff_index)
 
-    def _parse_diff(self, diff_index) -> List[Modification]:
+    def _parse_diff(self, diff_index) -> List[ModifiedFile]:
         modifications_list = []
         for diff in diff_index:
             old_path = diff.a_path
@@ -648,32 +712,35 @@ class Commit:
             change_type = self._from_change_to_modification_type(diff)
 
             diff_and_sc = {
-                'diff': self._get_decoded_str(diff.diff),
-                'source_code_before': self._get_decoded_sc_str(
-                    diff.a_blob),
-                'source_code': self._get_decoded_sc_str(
-                    diff.b_blob)
+                "diff": self._get_decoded_str(diff.diff),
+                "source_code_before": self._get_decoded_sc_str(diff.a_blob),
+                "source_code": self._get_decoded_sc_str(diff.b_blob),
             }
 
-            modifications_list.append(Modification(old_path, new_path,
-                                                   change_type, diff_and_sc))
+            modifications_list.append(
+                ModifiedFile(old_path, new_path, change_type, diff_and_sc)
+            )
 
         return modifications_list
 
     def _get_decoded_str(self, diff):
         try:
-            return diff.decode('utf-8', 'ignore')
-        except (UnicodeDecodeError, AttributeError, ValueError):
-            logger.debug('Could not load the diff of a '
-                         'file in commit %s', self._c_object.hexsha)
+            return diff.decode("utf-8", "ignore")
+        except (AttributeError, ValueError):
+            logger.debug(
+                "Could not load the diff of a " "file in commit %s",
+                self._c_object.hexsha,
+            )
             return None
 
     def _get_decoded_sc_str(self, diff):
         try:
-            return diff.data_stream.read().decode('utf-8', 'ignore')
-        except (UnicodeDecodeError, AttributeError, ValueError):
-            logger.debug('Could not load source code of a '
-                         'file in commit %s', self._c_object.hexsha)
+            return diff.data_stream.read().decode("utf-8", "ignore")
+        except (AttributeError, ValueError):
+            logger.debug(
+                "Could not load source code of a " "file in commit %s",
+                self._c_object.hexsha,
+            )
             return None
 
     @property
@@ -683,7 +750,7 @@ class Commit:
 
         :return: bool in_main_branch
         """
-        return self._conf.get('main_branch') in self.branches
+        return self._conf.get("main_branch") in self.branches
 
     @property
     def branches(self) -> Set[str]:
@@ -699,15 +766,15 @@ class Commit:
         return self._branches
 
     def _get_branches(self):
-        c_git = Git(str(self._conf.get('path_to_repo')))
+        c_git = Git(str(self._conf.get("path_to_repo")))
         branches = set()
-        args = ['--contains', self.hash]
+        args = ["--contains", self.hash]
         if self._conf.get("include_remotes"):
             args = ["-r"] + args
         if self._conf.get("include_refs"):
             args = ["-a"] + args
-        for branch in set(c_git.branch(*args).split('\n')):
-            branches.add(branch.strip().replace('* ', ''))
+        for branch in set(c_git.branch(*args).split("\n")):
+            branches.add(branch.strip().replace("* ", ""))
         return branches
 
     @property
@@ -761,7 +828,7 @@ class Commit:
 
         :return: The dmm value (between 0.0 and 1.0) for method interfacing in this commit.
                   or None if none of the programming languages in the commit are supported.
-       """
+        """
         return self._delta_maintainability(DMMProperty.UNIT_INTERFACING)
 
     def _delta_maintainability(self, dmm_prop: DMMProperty) -> Optional[float]:
@@ -780,7 +847,9 @@ class Commit:
             return self._good_change_proportion(delta_low, delta_high)
         return None
 
-    def _delta_risk_profile(self, dmm_prop: DMMProperty) -> Optional[Tuple[int, int]]:
+    def _delta_risk_profile(
+        self, dmm_prop: DMMProperty
+    ) -> Optional[Tuple[int, int]]:
         """
         Return the delta risk profile of this commit, which a pair (dv1, dv2), where
         dv1 is the total change in volume (lines of code) of low risk methods, and
@@ -789,16 +858,23 @@ class Commit:
         :param dmm_prop: Property indicating the type of risk
         :return: total delta risk profile for this commit.
         """
-        supported_modifications = [mod for mod in self.modifications if mod.language_supported]
+        supported_modifications = [
+            mod for mod in self.modified_files if mod.language_supported
+        ]
         if supported_modifications:
-            deltas = [mod._delta_risk_profile(dmm_prop) for mod in supported_modifications]
+            deltas = [
+                mod._delta_risk_profile(dmm_prop)
+                for mod in supported_modifications
+            ]
             delta_low = sum(dlow for (dlow, dhigh) in deltas)
             delta_high = sum(dhigh for (dlow, dhigh) in deltas)
             return delta_low, delta_high
         return None
 
     @staticmethod
-    def _good_change_proportion(low_risk_delta: int, high_risk_delta: int) -> Optional[float]:
+    def _good_change_proportion(
+        low_risk_delta: int, high_risk_delta: int
+    ) -> Optional[float]:
         """
         Given a delta risk profile, compute the proportion of "good" change in the total change.
         Increasing low risk code, or decreasing high risk code, is considered good.
diff --git a/pydriller/git_repository.py b/pydriller/git.py
similarity index 96%
rename from pydriller/git_repository.py
rename to pydriller/git.py
index a9b3c29..f6433c7 100644
--- a/pydriller/git_repository.py
+++ b/pydriller/git.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 """
-This module includes 1 class, GitRepository, representing a repository in Git.
+This module includes 1 class, Git, representing a repository in Git.
 """
 
 import logging
@@ -23,13 +23,13 @@ from typing import List, Dict, Set, Generator
 
 from git import Repo, GitCommandError, Commit as GitCommit
 
-from pydriller.domain.commit import Commit, ModificationType, Modification
+from pydriller.domain.commit import Commit, ModificationType, ModifiedFile
 from pydriller.utils.conf import Conf
 
 logger = logging.getLogger(__name__)
 
 
-class GitRepository:
+class Git:
     """
     Class representing a repository in Git. It contains most of the logic of
     PyDriller: obtaining the list of commits, checkout, reset, etc.
@@ -37,7 +37,7 @@ class GitRepository:
 
     def __init__(self, path: str, conf=None):
         """
-        Init the Git RepositoryMining.
+        Init the Git Repository.
 
         :param str path: path to the repository
         """
@@ -50,12 +50,15 @@ class GitRepository:
         if conf is None:
             conf = Conf({
                 "path_to_repo": str(self.path),
-                "git_repo": self
+                "git": self
             })
 
         self._conf = conf
         self._conf.set_value("main_branch", None)  # init main_branch to None
 
+        # Initialize repository
+        self._open_repository()
+
     @property
     def repo(self) -> Repo:
         """
@@ -203,7 +206,7 @@ class GitRepository:
         return tags
 
     def get_commits_last_modified_lines(self, commit: Commit,
-                                        modification: Modification = None,
+                                        modification: ModifiedFile = None,
                                         hashes_to_ignore_path: str = None) \
             -> Dict[str, Set[str]]:
         """
@@ -232,13 +235,13 @@ class GitRepository:
         if modification is not None:
             modifications = [modification]
         else:
-            modifications = commit.modifications
+            modifications = commit.modified_files
 
         return self._calculate_last_commits(commit, modifications,
                                             hashes_to_ignore_path)
 
     def _calculate_last_commits(self, commit: Commit,
-                                modifications: List[Modification],
+                                modifications: List[ModifiedFile],
                                 hashes_to_ignore_path: str = None) \
             -> Dict[str, Set[str]]:
 
diff --git a/pydriller/metrics/process/change_set.py b/pydriller/metrics/process/change_set.py
index 0aa6228..9ea69c2 100644
--- a/pydriller/metrics/process/change_set.py
+++ b/pydriller/metrics/process/change_set.py
@@ -29,7 +29,7 @@ class ChangeSet(ProcessMetric):
         self.committed_together = []
 
         for commit in self.repo_miner.traverse_commits():
-            self.committed_together.append(len(commit.modifications))
+            self.committed_together.append(len(commit.modified_files))
 
     def max(self):
         """
diff --git a/pydriller/metrics/process/code_churn.py b/pydriller/metrics/process/code_churn.py
index bff0fce..dfdde34 100644
--- a/pydriller/metrics/process/code_churn.py
+++ b/pydriller/metrics/process/code_churn.py
@@ -35,14 +35,14 @@ class CodeChurn(ProcessMetric):
 
         for commit in self.repo_miner.traverse_commits():
 
-            for modified_file in commit.modifications:
+            for modified_file in commit.modified_files:
 
                 filepath = renamed_files.get(modified_file.new_path, modified_file.new_path)
 
                 if modified_file.change_type == ModificationType.RENAME:
                     renamed_files[modified_file.old_path] = filepath
 
-                churn = modified_file.added - modified_file.removed
+                churn = modified_file.added_lines - modified_file.deleted_lines
                 self.files.setdefault(filepath, []).append(churn)
 
     def count(self):
@@ -51,7 +51,7 @@ class CodeChurn(ProcessMetric):
 
         :return: int number of churns
         """
-        count = dict()
+        count = {}
         for path, churns in self.files.items():
             count[path] = sum(churns)
 
@@ -63,7 +63,7 @@ class CodeChurn(ProcessMetric):
 
         :return: int max number of churns
         """
-        max_count = dict()
+        max_count = {}
         for path, churns in self.files.items():
             max_count[path] = max(churns)
 
@@ -75,7 +75,7 @@ class CodeChurn(ProcessMetric):
 
         :return: int avg number of churns rounded off to the nearest integer
         """
-        avg_count = dict()
+        avg_count = {}
         for path, churns in self.files.items():
             avg_count[path] = round(statistics.mean(churns))
 
diff --git a/pydriller/metrics/process/commits_count.py b/pydriller/metrics/process/commits_count.py
index f057d33..9251f95 100644
--- a/pydriller/metrics/process/commits_count.py
+++ b/pydriller/metrics/process/commits_count.py
@@ -18,7 +18,7 @@ class CommitsCount(ProcessMetric):
 
         for commit in self.repo_miner.traverse_commits():
 
-            for modified_file in commit.modifications:
+            for modified_file in commit.modified_files:
 
                 filepath = renamed_files.get(modified_file.new_path,
                                              modified_file.new_path)
diff --git a/pydriller/metrics/process/contributors_count.py b/pydriller/metrics/process/contributors_count.py
index 1c9f64e..7450555 100644
--- a/pydriller/metrics/process/contributors_count.py
+++ b/pydriller/metrics/process/contributors_count.py
@@ -30,14 +30,14 @@ class ContributorsCount(ProcessMetric):
 
     def _initialize(self):
 
-        self.contributors = dict()
-        self.minor_contributors = dict()
+        self.contributors = {}
+        self.minor_contributors = {}
 
         renamed_files = {}
 
         for commit in self.repo_miner.traverse_commits():
 
-            for modified_file in commit.modifications:
+            for modified_file in commit.modified_files:
 
                 filepath = renamed_files.get(modified_file.new_path,
                                              modified_file.new_path)
@@ -46,7 +46,7 @@ class ContributorsCount(ProcessMetric):
                     renamed_files[modified_file.old_path] = filepath
 
                 author = commit.author.email.strip()
-                lines_authored = modified_file.added + modified_file.removed
+                lines_authored = modified_file.added_lines + modified_file.deleted_lines
 
                 self.contributors[filepath] = self.contributors.get(filepath, {})
                 self.contributors[filepath][author] = self.contributors[filepath].get(author, 0) + lines_authored
diff --git a/pydriller/metrics/process/contributors_experience.py b/pydriller/metrics/process/contributors_experience.py
index 7542a66..badfe7c 100644
--- a/pydriller/metrics/process/contributors_experience.py
+++ b/pydriller/metrics/process/contributors_experience.py
@@ -26,7 +26,7 @@ class ContributorsExperience(ProcessMetric):
 
         for commit in self.repo_miner.traverse_commits():
 
-            for modified_file in commit.modifications:
+            for modified_file in commit.modified_files:
 
                 filepath = renamed_files.get(modified_file.new_path,
                                              modified_file.new_path)
@@ -35,7 +35,7 @@ class ContributorsExperience(ProcessMetric):
                     renamed_files[modified_file.old_path] = filepath
 
                 author = commit.author.email.strip()
-                lines_authored = modified_file.added + modified_file.removed
+                lines_authored = modified_file.added_lines + modified_file.deleted_lines
 
                 files[filepath] = files.get(filepath, {})
                 files[filepath][author] = files[filepath].get(author, 0) + \
diff --git a/pydriller/metrics/process/history_complexity.py b/pydriller/metrics/process/history_complexity.py
index 40d01ff..00574cc 100644
--- a/pydriller/metrics/process/history_complexity.py
+++ b/pydriller/metrics/process/history_complexity.py
@@ -46,14 +46,14 @@ class HistoryComplexity(ProcessMetric):
 
         for commit in self.repo_miner.traverse_commits():
 
-            for modified_file in commit.modifications:
+            for modified_file in commit.modified_files:
                 filepath = renamed_files.get(modified_file.new_path,
                                              modified_file.new_path)
 
                 if modified_file.change_type == ModificationType.RENAME:
                     renamed_files[modified_file.old_path] = filepath
 
-                modifications = modified_file.added + modified_file.removed
+                modifications = modified_file.added_lines + modified_file.deleted_lines
                 if modifications:
                     files[filepath] = files.get(filepath, 0) + modifications
 
diff --git a/pydriller/metrics/process/hunks_count.py b/pydriller/metrics/process/hunks_count.py
index f212bb4..8b7fba0 100644
--- a/pydriller/metrics/process/hunks_count.py
+++ b/pydriller/metrics/process/hunks_count.py
@@ -29,7 +29,7 @@ class HunksCount(ProcessMetric):
 
         for commit in self.repo_miner.traverse_commits():
 
-            for modified_file in commit.modifications:
+            for modified_file in commit.modified_files:
 
                 filepath = renamed_files.get(modified_file.new_path,
                                              modified_file.new_path)
diff --git a/pydriller/metrics/process/lines_count.py b/pydriller/metrics/process/lines_count.py
index c29f928..61b06a2 100644
--- a/pydriller/metrics/process/lines_count.py
+++ b/pydriller/metrics/process/lines_count.py
@@ -45,13 +45,13 @@ class LinesCount(ProcessMetric):
 
     def _initialize(self):
 
-        self.lines_added = dict()
-        self.lines_removed = dict()
+        self.lines_added = {}
+        self.lines_removed = {}
 
         renamed_files = {}
         for commit in self.repo_miner.traverse_commits():
 
-            for modified_file in commit.modifications:
+            for modified_file in commit.modified_files:
 
                 filepath = renamed_files.get(modified_file.new_path,
                                              modified_file.new_path)
@@ -59,8 +59,8 @@ class LinesCount(ProcessMetric):
                 if modified_file.change_type == ModificationType.RENAME:
                     renamed_files[modified_file.old_path] = filepath
 
-                self.lines_added.setdefault(filepath, []).append(modified_file.added)
-                self.lines_removed.setdefault(filepath, []).append(modified_file.removed)
+                self.lines_added.setdefault(filepath, []).append(modified_file.added_lines)
+                self.lines_removed.setdefault(filepath, []).append(modified_file.deleted_lines)
 
     def count(self):
         """
@@ -68,7 +68,7 @@ class LinesCount(ProcessMetric):
 
         :return: int lines added + lines removed
         """
-        count = dict()
+        count = {}
 
         for path, lines in self.lines_added.items():
             count[path] = count.get(path, 0) + sum(lines)
@@ -84,7 +84,7 @@ class LinesCount(ProcessMetric):
 
         :return: int lines added
         """
-        count = dict()
+        count = {}
         for path, lines in self.lines_added.items():
             count[path] = sum(lines)
 
@@ -96,7 +96,7 @@ class LinesCount(ProcessMetric):
 
         :return: int max number of lines added
         """
-        maximum = dict()
+        maximum = {}
         for path, lines in self.lines_added.items():
             maximum[path] = max(lines)
 
@@ -108,7 +108,7 @@ class LinesCount(ProcessMetric):
 
         :return: int avg number of lines rounded off to the nearest integer
         """
-        avg = dict()
+        avg = {}
         for path, lines in self.lines_added.items():
             avg[path] = round(statistics.mean(lines))
 
@@ -120,7 +120,7 @@ class LinesCount(ProcessMetric):
 
         :return: int lines removed
         """
-        count = dict()
+        count = {}
         for path, lines in self.lines_removed.items():
             count[path] = sum(lines)
 
@@ -132,7 +132,7 @@ class LinesCount(ProcessMetric):
 
         :return: int max number of lines removed
         """
-        maximum = dict()
+        maximum = {}
         for path, lines in self.lines_removed.items():
             maximum[path] = max(lines)
 
@@ -144,7 +144,7 @@ class LinesCount(ProcessMetric):
 
         :return: int rounded off to the nearest integer
         """
-        avg = dict()
+        avg = {}
         for path, lines in self.lines_removed.items():
             avg[path] = round(statistics.mean(lines))
 
diff --git a/pydriller/metrics/process/process_metric.py b/pydriller/metrics/process/process_metric.py
index 9d77823..cc102e1 100644
--- a/pydriller/metrics/process/process_metric.py
+++ b/pydriller/metrics/process/process_metric.py
@@ -3,7 +3,7 @@ This module contains the abstract class to implement process metrics.
 """
 
 from datetime import datetime
-from pydriller import RepositoryMining
+from pydriller import Repository
 
 
 class ProcessMetric:
@@ -35,15 +35,15 @@ class ProcessMetric:
             raise TypeError('You must pass one between to and to_commit')
 
         if from_commit and to_commit and from_commit == to_commit:  # Use 'single' param to avoid Warning
-            self.repo_miner = RepositoryMining(path_to_repo, single=from_commit)
+            self.repo_miner = Repository(path_to_repo, single=from_commit)
 
         else:
-            self.repo_miner = RepositoryMining(path_to_repo=path_to_repo,
-                                               since=since,
-                                               to=to,
-                                               from_commit=from_commit,
-                                               to_commit=to_commit,
-                                               order='reverse')
+            self.repo_miner = Repository(path_to_repo=path_to_repo,
+                                         since=since,
+                                         to=to,
+                                         from_commit=from_commit,
+                                         to_commit=to_commit,
+                                         order='reverse')
 
     def count(self):
         """
diff --git a/pydriller/repository_mining.py b/pydriller/repository.py
similarity index 78%
rename from pydriller/repository_mining.py
rename to pydriller/repository.py
index d78846a..1adf822 100644
--- a/pydriller/repository_mining.py
+++ b/pydriller/repository.py
@@ -13,13 +13,15 @@
 # limitations under the License.
 
 """
-This module includes 1 class, RepositoryMining, main class of PyDriller.
+This module includes 1 class, Repository, main class of PyDriller.
 """
 
 import logging
+import math
 import os
 import shutil
 import tempfile
+import concurrent.futures
 from contextlib import contextmanager
 from datetime import datetime
 from pathlib import Path
@@ -28,13 +30,13 @@ from typing import List, Generator, Union
 from git import Repo
 
 from pydriller.domain.commit import Commit
-from pydriller.git_repository import GitRepository
+from pydriller.git import Git
 from pydriller.utils.conf import Conf
 
 logger = logging.getLogger(__name__)
 
 
-class RepositoryMining:
+class Repository:
     """
     This is the main class of PyDriller, responsible for running the study.
     """
@@ -46,6 +48,7 @@ class RepositoryMining:
                  from_tag: str = None, to_tag: str = None,
                  include_refs: bool = False,
                  include_remotes: bool = False,
+                 num_workers: int = 1,
                  only_in_branch: str = None,
                  only_modifications_with_file_types: List[str] = None,
                  only_no_merge: bool = False,
@@ -58,7 +61,7 @@ class RepositoryMining:
                  clone_repo_to: str = None,
                  order: str = None):
         """
-        Init a repository mining. The only required parameter is
+        Init a repository. The only required parameter is
         "path_to_repo": to analyze a single repo, pass the absolute path to
         the repo; if you need to analyze more repos, pass a list of absolute
         paths.
@@ -106,7 +109,7 @@ class RepositoryMining:
             )
 
         options = {
-            "git_repo": None,
+            "git": None,
             "path_to_repo": path_to_repo,
             "from_commit": from_commit,
             "to_commit": to_commit,
@@ -117,6 +120,7 @@ class RepositoryMining:
             "single": single,
             "include_refs": include_refs,
             "include_remotes": include_remotes,
+            "num_workers": num_workers,
             "only_in_branch": only_in_branch,
             "only_modifications_with_file_types": file_modification_set,
             "only_no_merge": only_no_merge,
@@ -160,7 +164,7 @@ class RepositoryMining:
         return clone_folder
 
     @contextmanager
-    def _prep_repo(self, path_repo: str) -> Generator[GitRepository, None, None]:
+    def _prep_repo(self, path_repo: str) -> Generator[Git, None, None]:
         local_path_repo = path_repo
         if self._is_remote(path_repo):
             local_path_repo = self._clone_remote_repo(self._clone_folder(), path_repo)
@@ -170,18 +174,18 @@ class RepositoryMining:
         # of which one we are currently analyzing
         self._conf.set_value('path_to_repo', local_path_repo)
 
-        self.git_repo = GitRepository(local_path_repo, self._conf)
-        # saving the GitRepository object for further use
-        self._conf.set_value("git_repo", self.git_repo)
+        self.git = Git(local_path_repo, self._conf)
+        # saving the Git object for further use
+        self._conf.set_value("git", self.git)
 
         # checking that the filters are set correctly
         self._conf.sanity_check_filters()
-        yield self.git_repo
+        yield self.git
 
         # cleaning, this is necessary since GitPython issues on memory leaks
-        self._conf.set_value("git_repo", None)
-        self.git_repo.clear()
-        self.git_repo = None  # type: ignore
+        self._conf.set_value("git", None)
+        self.git.clear()
+        self.git = None  # type: ignore
 
         # delete the temporary directory if created
         if self._is_remote(path_repo) and self._cleanup is True:
@@ -200,31 +204,60 @@ class RepositoryMining:
         a generator of commits.
         """
         for path_repo in self._conf.get('path_to_repos'):
-            with self._prep_repo(path_repo=path_repo) as git_repo:
-                logger.info('Analyzing git repository in %s', git_repo.path)
+            with self._prep_repo(path_repo=path_repo) as git:
+                logger.info('Analyzing git repository in %s', git.path)
 
                 # Get the commits that modified the filepath. In this case, we can not use
                 # git rev-list since it doesn't have the option --follow, necessary to follow
                 # the renames. Hence, we manually call git log instead
                 if self._conf.get('filepath') is not None:
-                    self._conf.set_value('filepath_commits', git_repo.get_commits_modified_file(self._conf.get('filepath')))
+                    self._conf.set_value('filepath_commits', git.get_commits_modified_file(self._conf.get('filepath')))
 
                 # Gets only the commits that are tagged
                 if self._conf.get('only_releases'):
-                    self._conf.set_value('tagged_commits', git_repo.get_tagged_commits())
+                    self._conf.set_value('tagged_commits', git.get_tagged_commits())
 
                 # Build the arguments to pass to git rev-list.
                 rev, kwargs = self._conf.build_args()
 
-                # Iterate over all the commits returned by git rev-list
-                for commit in git_repo.get_list_commits(rev, **kwargs):
-                    logger.info('Commit #%s in %s from %s', commit.hash, commit.committer_date, commit.author.name)
+                commits_list = list(git.get_list_commits(rev, **kwargs))
 
-                    if self._conf.is_commit_filtered(commit):
-                        logger.info('Commit #%s filtered', commit.hash)
-                        continue
+                if not commits_list:
+                    return
 
-                    yield commit
+                chunks = self._split_in_chunks(commits_list, self._conf.get("num_workers"))
+                with concurrent.futures.ThreadPoolExecutor(max_workers=self._conf.get("num_workers")) as executor:
+                    jobs = {executor.submit(self._iter_commits, chunk): chunk for chunk in chunks}
+
+                    for job in concurrent.futures.as_completed(jobs):
+                        for commit in job.result():
+                            yield commit
+
+    def _iter_commits(self, commits_list: List[Commit]) -> Generator[Commit, None, None]:
+        for commit in commits_list:
+            logger.info('Commit #%s in %s from %s', commit.hash, commit.committer_date, commit.author.name)
+
+            if self._conf.is_commit_filtered(commit):
+                logger.info('Commit #%s filtered', commit.hash)
+                continue
+
+            yield commit
+
+    @staticmethod
+    def _split_in_chunks(full_list: List[Commit], num_workers: int) -> List[List[Commit]]:
+        """
+        Given the list of commits return chunks of commits based on the number of workers.
+
+        :param List[Commit] full_list: full list of commits
+        :param int num_workers: number of workers (i.e., threads)
+        :return: Chunks of commits
+        """
+        num_chunks = math.ceil(len(full_list) / num_workers)
+        chunks = []
+        for i in range(0, len(full_list), num_chunks):
+            chunks.append(full_list[i:i + num_chunks])
+
+        return chunks
 
     @staticmethod
     def _get_repo_name_from_url(url: str) -> str:
diff --git a/pydriller/utils/conf.py b/pydriller/utils/conf.py
index 78cc416..8894172 100644
--- a/pydriller/utils/conf.py
+++ b/pydriller/utils/conf.py
@@ -105,7 +105,7 @@ class Conf:
                 raise Exception('You can not specify a single commit with '
                                 'other filters')
             try:
-                self.set_value('single', self.get("git_repo").get_commit(self.get('single')).hash)
+                self.set_value('single', self.get("git").get_commit(self.get('single')).hash)
             except BadName:
                 raise Exception("The commit {} defined in "
                                 "the 'single' filtered does "
@@ -117,8 +117,8 @@ class Conf:
         """
         if self.get('from_commit') and self.get('to_commit'):
             chronological_order = self._is_commit_before(
-                self.get('git_repo').get_commit(self.get('from_commit')),
-                self.get('git_repo').get_commit(self.get('to_commit')))
+                self.get('git').get_commit(self.get('from_commit')),
+                self.get('git').get_commit(self.get('to_commit')))
 
             if not chronological_order:
                 self._swap_commit_fiters()
@@ -147,11 +147,11 @@ class Conf:
         from_tag = self.get('from_tag')
         from_commit = self.get('from_commit')
         if from_tag is not None:
-            tagged_commit = self.get("git_repo").get_commit_from_tag(from_tag)
+            tagged_commit = self.get("git").get_commit_from_tag(from_tag)
             from_commit = tagged_commit.hash
         if from_commit is not None:
             try:
-                commit = self.get("git_repo").get_commit(from_commit)
+                commit = self.get("git").get_commit(from_commit)
                 if len(commit.parents) == 0:
                     return [commit.hash]
                 elif len(commit.parents) == 1:
@@ -169,11 +169,11 @@ class Conf:
         to_tag = self.get('to_tag')
         to_commit = self.get('to_commit')
         if to_tag is not None:
-            tagged_commit = self.get("git_repo").get_commit_from_tag(to_tag)
+            tagged_commit = self.get("git").get_commit_from_tag(to_tag)
             to_commit = tagged_commit.hash
         if to_commit is not None:
             try:
-                return self.get("git_repo").get_commit(to_commit).hash
+                return self.get("git").get_commit(to_commit).hash
             except Exception:
                 raise Exception("The commit {} defined in the 'to_tag' or 'to_commit' filter does "
                                 "not exist".format(self.get('to_commit')))
@@ -279,7 +279,7 @@ class Conf:
         return False
 
     def _has_modification_with_file_type(self, commit):
-        for mod in commit.modifications:
+        for mod in commit.modified_files:
             if mod.filename.endswith(tuple(self.get('only_modifications_with_file_types'))):
                 return True
         return False


Test patch:
diff --git a/tests/integration/test_between_dates.py b/tests/integration/test_between_dates.py
index 95a1a4e..eeab7ff 100644
--- a/tests/integration/test_between_dates.py
+++ b/tests/integration/test_between_dates.py
@@ -15,7 +15,7 @@
 import logging
 from datetime import datetime, timezone, timedelta
 
-from pydriller.repository_mining import RepositoryMining
+from pydriller.repository import Repository
 
 logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s',
                     level=logging.INFO)
@@ -26,9 +26,9 @@ dt2 = datetime(2016, 10, 8, 17, 59, 0, tzinfo=to_zone)
 
 
 def test_between_dates():
-    list_commits = list(RepositoryMining('test-repos/different_files',
-                                         since=dt1,
-                                         to=dt2).traverse_commits())
+    list_commits = list(Repository('test-repos/different_files',
+                                   since=dt1,
+                                   to=dt2).traverse_commits())
 
     assert len(list_commits) == 2
     assert list_commits[0].hash == 'a1b6136f978644ff1d89816bc0f2bd86f6d9d7f5'
@@ -38,9 +38,9 @@ def test_between_dates():
 def test_between_dates_without_timezone():
     dt1 = datetime(2016, 10, 8, 21, 0, 0)
     dt2 = datetime(2016, 10, 8, 21, 59, 0)
-    list_commits = list(RepositoryMining('test-repos/different_files',
-                                         since=dt1,
-                                         to=dt2).traverse_commits())
+    list_commits = list(Repository('test-repos/different_files',
+                                   since=dt1,
+                                   to=dt2).traverse_commits())
 
     assert len(list_commits) == 2
     assert list_commits[0].hash == 'a1b6136f978644ff1d89816bc0f2bd86f6d9d7f5'
@@ -48,10 +48,10 @@ def test_between_dates_without_timezone():
 
 
 def test_between_dates_reversed():
-    lc = list(RepositoryMining('test-repos/different_files',
-                               since=dt1,
-                               to=dt2,
-                               order='reverse').traverse_commits())
+    lc = list(Repository('test-repos/different_files',
+                         since=dt1,
+                         to=dt2,
+                         order='reverse').traverse_commits())
 
     assert len(lc) == 2
     assert lc[0].hash == '375de7a8275ecdc0b28dc8de2568f47241f443e9'
diff --git a/tests/integration/test_between_tags.py b/tests/integration/test_between_tags.py
index 45bef39..c52efab 100644
--- a/tests/integration/test_between_tags.py
+++ b/tests/integration/test_between_tags.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from pydriller.repository_mining import RepositoryMining
+from pydriller.repository import Repository
 import logging
 
 logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
@@ -22,9 +22,9 @@ def test_between_revisions():
     from_tag = 'tag1'
     to_tag = 'tag3'
 
-    lc = list(RepositoryMining('test-repos/tags',
-                               from_tag=from_tag,
-                               to_tag=to_tag).traverse_commits())
+    lc = list(Repository('test-repos/tags',
+                         from_tag=from_tag,
+                         to_tag=to_tag).traverse_commits())
 
     assert len(lc) == 5
     assert '6bb9e2c6a8080e6b5b34e6e316c894b2ddbf7fcd' == lc[0].hash
@@ -42,7 +42,7 @@ def test_multiple_repos_with_tags():
         'test-repos/tags',
         'test-repos/tags'
     ]
-    lc = list(RepositoryMining(path_to_repo=repos,
-                               from_tag=from_tag,
-                               to_tag=to_tag).traverse_commits())
+    lc = list(Repository(path_to_repo=repos,
+                         from_tag=from_tag,
+                         to_tag=to_tag).traverse_commits())
     assert len(lc) == 9
diff --git a/tests/integration/test_commit_filters.py b/tests/integration/test_commit_filters.py
index 0a610e2..5f3678b 100644
--- a/tests/integration/test_commit_filters.py
+++ b/tests/integration/test_commit_filters.py
@@ -14,7 +14,7 @@
 
 import logging
 from datetime import datetime, timezone, timedelta
-from pydriller.repository_mining import RepositoryMining
+from pydriller.repository import Repository
 from git import Repo
 
 import pytest
@@ -24,15 +24,15 @@ logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s',
 
 
 def test_mod_with_file_types():
-    lc = list(RepositoryMining('test-repos/different_files',
-                               only_modifications_with_file_types=['.java']).traverse_commits())
+    lc = list(Repository('test-repos/different_files',
+                         only_modifications_with_file_types=['.java']).traverse_commits())
 
     assert len(lc) == 2
     assert lc[0].hash == 'a1b6136f978644ff1d89816bc0f2bd86f6d9d7f5'
     assert lc[1].hash == 'b8c2be250786975f1c6f47e96922096f1bb25e39'
 
-    lc = list(RepositoryMining('test-repos/different_files1',
-                               only_modifications_with_file_types=['.java'])
+    lc = list(Repository('test-repos/different_files1',
+                         only_modifications_with_file_types=['.java'])
               .traverse_commits())
 
     assert len(lc) == 2
@@ -41,8 +41,8 @@ def test_mod_with_file_types():
 
 
 def test_mod_with_file_types_no_extension():
-    lc = list(RepositoryMining('test-repos/different_files',
-                               only_modifications_with_file_types=['.py'])
+    lc = list(Repository('test-repos/different_files',
+                         only_modifications_with_file_types=['.py'])
               .traverse_commits())
 
     assert len(lc) == 0
@@ -52,9 +52,9 @@ def test_mod_with_file_types_and_date():
     to_zone = timezone(timedelta(hours=2))
     dt1 = datetime(2016, 10, 8, 23, 57, 49, tzinfo=to_zone)
     print(dt1)
-    lc = list(RepositoryMining('test-repos/different_files',
-                               only_modifications_with_file_types=['.java'],
-                               since=dt1)
+    lc = list(Repository('test-repos/different_files',
+                         only_modifications_with_file_types=['.java'],
+                         since=dt1)
               .traverse_commits())
 
     print(lc)
@@ -63,7 +63,7 @@ def test_mod_with_file_types_and_date():
 
 
 def test_only_in_main_branch():
-    lc = list(RepositoryMining('test-repos/branches_not_merged').traverse_commits())
+    lc = list(Repository('test-repos/branches_not_merged').traverse_commits())
 
     assert len(lc) == 3
     assert lc[0].hash == '04b0af7b53c2a0095e98951571aa41c2e0e0dbec'
@@ -72,8 +72,8 @@ def test_only_in_main_branch():
 
 
 def test_only_no_merge():
-    lc = list(RepositoryMining('test-repos/branches_merged',
-                               only_no_merge=True).traverse_commits())
+    lc = list(Repository('test-repos/branches_merged',
+                         only_no_merge=True).traverse_commits())
 
     assert len(lc) == 3
     assert lc[0].hash == '168b3aab057ed61a769acf336a4ef5e64f76c9fd'
@@ -82,7 +82,7 @@ def test_only_no_merge():
 
 
 def test_no_filters():
-    lc = list(RepositoryMining('test-repos/different_files').traverse_commits())
+    lc = list(Repository('test-repos/different_files').traverse_commits())
 
     assert len(lc) == 3
     assert lc[0].hash == 'a1b6136f978644ff1d89816bc0f2bd86f6d9d7f5'
@@ -91,8 +91,8 @@ def test_no_filters():
 
 
 def test_only_in_branch():
-    lc = list(RepositoryMining('test-repos/branches_not_merged',
-                               only_in_branch='b1').traverse_commits())
+    lc = list(Repository('test-repos/branches_not_merged',
+                         only_in_branch='b1').traverse_commits())
     assert len(lc) == 5
 
     assert lc[0].hash == '04b0af7b53c2a0095e98951571aa41c2e0e0dbec'
@@ -104,124 +104,123 @@ def test_only_in_branch():
 
 def test_only_in_branches():
     # by default, only analyze master
-    assert len(list(RepositoryMining('test-repos/branches_not_merged')
+    assert len(list(Repository('test-repos/branches_not_merged')
                     .traverse_commits())) == 3
     # only analyze b2
-    assert len(list(RepositoryMining('test-repos/branches_not_merged',
-                                     only_in_branch='b2')
+    assert len(list(Repository('test-repos/branches_not_merged',
+                               only_in_branch='b2')
                     .traverse_commits())) == 4
     # only analyze b1
-    assert len(list(RepositoryMining('test-repos/branches_not_merged',
-                                     only_in_branch='b1')
+    assert len(list(Repository('test-repos/branches_not_merged',
+                               only_in_branch='b1')
                     .traverse_commits())) == 5
 
 
 def test_only_in_branch_not_exist():
     with pytest.raises(Exception):
-        list(RepositoryMining('test-repos/branches_not_merged', only_in_branch='branch2')
+        list(Repository('test-repos/branches_not_merged', only_in_branch='branch2')
              .traverse_commits())
 
 
 def test_only_authors():
-    lc = list(RepositoryMining('test-repos/multiple_authors',
-                               only_authors=["Maurício Aniche"])
+    lc = list(Repository('test-repos/multiple_authors',
+                         only_authors=["Maurício Aniche"])
               .traverse_commits())
     assert len(lc) == 4
 
-    lc = list(RepositoryMining('test-repos/multiple_authors',
-                               only_authors=["ishepard"])
+    lc = list(Repository('test-repos/multiple_authors',
+                         only_authors=["ishepard"])
               .traverse_commits())
     assert len(lc) == 1
 
 
 def test_only_authors_not_existing():
-    lc = list(RepositoryMining('test-repos/multiple_authors',
-                               only_authors=["Uncle Bob"])
+    lc = list(Repository('test-repos/multiple_authors',
+                         only_authors=["Uncle Bob"])
               .traverse_commits())
     assert len(lc) == 0
 
 
 def test_only_commits():
-    lc = list(RepositoryMining('test-repos/complex_repo',
-                               only_commits=["9e71dd5726d775fb4a5f08506a539216e878adbb"]).traverse_commits())
+    lc = list(Repository('test-repos/complex_repo',
+                         only_commits=["9e71dd5726d775fb4a5f08506a539216e878adbb"]).traverse_commits())
     assert len(lc) == 1
     assert lc[0].hash == "9e71dd5726d775fb4a5f08506a539216e878adbb"
 
-    lc = list(RepositoryMining('test-repos/complex_repo',
-                               only_commits=["953737b199de233896f00b4d87a0bc2794317253",
-                                             "ffccf1e7497eb8136fd66ed5e42bef29677c4b71"]).traverse_commits())
+    lc = list(Repository('test-repos/complex_repo',
+                         only_commits=["953737b199de233896f00b4d87a0bc2794317253", "ffccf1e7497eb8136fd66ed5e42bef29677c4b71"]).traverse_commits())
     assert len(lc) == 2
     assert lc[0].hash == "ffccf1e7497eb8136fd66ed5e42bef29677c4b71"
     assert lc[1].hash == "953737b199de233896f00b4d87a0bc2794317253"
 
-    lc = list(RepositoryMining('test-repos/complex_repo',
-                               only_commits=["866e997a9e44cb4ddd9e00efe49361420aff2559",
-                                             "57dbd017d1a744b949e7ca0b1c1a3b3dd4c1cbc1",
-                                             "e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2"]).traverse_commits())
+    lc = list(Repository('test-repos/complex_repo',
+                         only_commits=["866e997a9e44cb4ddd9e00efe49361420aff2559",
+                                       "57dbd017d1a744b949e7ca0b1c1a3b3dd4c1cbc1",
+                                       "e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2"]).traverse_commits())
     assert len(lc) == 3
     assert lc[0].hash == "866e997a9e44cb4ddd9e00efe49361420aff2559"
     assert lc[1].hash == "57dbd017d1a744b949e7ca0b1c1a3b3dd4c1cbc1"
     assert lc[2].hash == "e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2"
 
-    lc = list(RepositoryMining('test-repos/complex_repo',
-                               only_commits=["fake hash"]).traverse_commits())
+    lc = list(Repository('test-repos/complex_repo',
+                         only_commits=["fake hash"]).traverse_commits())
     assert len(lc) == 0
 
-    total_commits = len(list(RepositoryMining('test-repos/complex_repo').traverse_commits()))
+    total_commits = len(list(Repository('test-repos/complex_repo').traverse_commits()))
 
     assert total_commits == 13
 
 
 def test_single_commit():
-    lc = list(RepositoryMining('test-repos/complex_repo',
-                               single="866e997a9e44cb4ddd9e00efe49361420aff2559").traverse_commits())
+    lc = list(Repository('test-repos/complex_repo',
+                         single="866e997a9e44cb4ddd9e00efe49361420aff2559").traverse_commits())
     assert len(lc) == 1
     assert lc[0].hash == "866e997a9e44cb4ddd9e00efe49361420aff2559"
 
-    lc = list(RepositoryMining('test-repos/complex_repo',
-                               single="ffccf1e7497eb8136fd66ed5e42bef29677c4b71").traverse_commits())
+    lc = list(Repository('test-repos/complex_repo',
+                         single="ffccf1e7497eb8136fd66ed5e42bef29677c4b71").traverse_commits())
     assert len(lc) == 1
     assert lc[0].hash == "ffccf1e7497eb8136fd66ed5e42bef29677c4b71"
 
 
 def test_single_commit_head():
-    lc = list(RepositoryMining('test-repos/complex_repo',
-                               single="e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2").traverse_commits())
+    lc = list(Repository('test-repos/complex_repo',
+                         single="e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2").traverse_commits())
     assert len(lc) == 1
 
-    lc_head = list(RepositoryMining('test-repos/complex_repo', single="HEAD").traverse_commits())
+    lc_head = list(Repository('test-repos/complex_repo', single="HEAD").traverse_commits())
     assert len(lc_head) == 1
     assert lc[0].hash == lc_head[0].hash
 
 
 def test_single_commit_not_existing_single_commit():
     with pytest.raises(Exception):
-        list(RepositoryMining('test-repos/complex_repo',
-                              single="ASD").traverse_commits())
+        list(Repository('test-repos/complex_repo',
+                        single="ASD").traverse_commits())
 
 
 def test_single_commit_not_existing_from_commit():
     with pytest.raises(Exception):
-        list(RepositoryMining('test-repos/complex_repo',
-                              from_commit="ASD").traverse_commits())
+        list(Repository('test-repos/complex_repo',
+                        from_commit="ASD").traverse_commits())
 
 
 def test_single_commit_not_existing_to_commit():
     with pytest.raises(Exception):
-        list(RepositoryMining('test-repos/complex_repo',
-                              to_commit="ASD").traverse_commits())
+        list(Repository('test-repos/complex_repo',
+                        to_commit="ASD").traverse_commits())
 
 
 def test_single_commit_not_existing_from_and_to_commit():
     with pytest.raises(Exception):
-        list(RepositoryMining('test-repos/complex_repo',
-                              from_commit="ASD",
-                              to_commit="ASD").traverse_commits())
+        list(Repository('test-repos/complex_repo',
+                        from_commit="ASD",
+                        to_commit="ASD").traverse_commits())
 
 
 def test_filepath_with_to():
     dt = datetime(2018, 6, 6)
-    assert len(list(RepositoryMining(
+    assert len(list(Repository(
         path_to_repo='test-repos/szz',
         filepath='myfolder/H.java',
         to=dt).traverse_commits())) == 5
@@ -229,7 +228,7 @@ def test_filepath_with_to():
 
 def test_filepath_with_since():
     since = datetime(2018, 6, 6)
-    assert len(list(RepositoryMining(
+    assert len(list(Repository(
         path_to_repo='test-repos/szz',
         filepath='myfolder/H.java',
         since=since).traverse_commits())) == 11
@@ -237,7 +236,7 @@ def test_filepath_with_since():
 
 def test_filepath_with_rename():
     dt = datetime(2018, 6, 6)
-    commits = list(RepositoryMining(
+    commits = list(Repository(
         path_to_repo='test-repos/small_repo',
         filepath='file4.java',
         to=dt).traverse_commits())
@@ -250,7 +249,7 @@ def test_filepath_with_rename():
 
 
 def test_filepath_with_rename_complex():
-    commits = list(RepositoryMining(
+    commits = list(Repository(
         path_to_repo='test-repos/complex_repo',
         filepath='Matricula.javax').traverse_commits())
     assert len(commits) == 6
@@ -266,8 +265,8 @@ def test_filepath_with_rename_complex():
 
 
 def test_only_releases():
-    lc = list(RepositoryMining('test-repos/tags',
-                               only_releases=True).traverse_commits())
+    lc = list(Repository('test-repos/tags',
+                         only_releases=True).traverse_commits())
 
     assert len(lc) == 3
     assert '6bb9e2c6a8080e6b5b34e6e316c894b2ddbf7fcd' == lc[0].hash
@@ -276,14 +275,14 @@ def test_only_releases():
 
 
 def test_only_releases_wo_releases():
-    lc = list(RepositoryMining('test-repos/complex_repo', only_releases=True).traverse_commits())
+    lc = list(Repository('test-repos/complex_repo', only_releases=True).traverse_commits())
 
     assert len(lc) == 0
 
 
 def test_date_order():
-    date_order = list(RepositoryMining('test-repos/order', order='date-order').traverse_commits())
-    author_date_order = list(RepositoryMining('test-repos/order', order='author-date-order').traverse_commits())
+    date_order = list(Repository('test-repos/order', order='date-order').traverse_commits())
+    author_date_order = list(Repository('test-repos/order', order='author-date-order').traverse_commits())
     assert '5e3cfa27b3fe6dd4d12fd89664fea9397141b843' == date_order[0].hash == author_date_order[0].hash
     assert '19732de9e2b58ba7285f272810a9d8ddf18e7c89' == date_order[1].hash == author_date_order[1].hash
     assert '78a94953a3e140f2d0027fb57963345fbf6d59fe' == date_order[2].hash == author_date_order[2].hash
@@ -295,7 +294,7 @@ def test_date_order():
 
 
 def test_topo_order():
-    topo_order = list(RepositoryMining('test-repos/order', order='topo-order').traverse_commits())
+    topo_order = list(Repository('test-repos/order', order='topo-order').traverse_commits())
 
     assert '5e3cfa27b3fe6dd4d12fd89664fea9397141b843' == topo_order[0].hash
     assert '19732de9e2b58ba7285f272810a9d8ddf18e7c89' == topo_order[1].hash
@@ -308,7 +307,7 @@ def test_topo_order():
 
 
 def test_should_visit_ascendent_order():
-    lc = list(RepositoryMining('test-repos/small_repo').traverse_commits())
+    lc = list(Repository('test-repos/small_repo').traverse_commits())
     assert len(lc) == 5
     assert lc[0].hash == 'a88c84ddf42066611e76e6cb690144e5357d132c'
     assert lc[1].hash == '6411e3096dd2070438a17b225f44475136e54e3a'
@@ -318,8 +317,8 @@ def test_should_visit_ascendent_order():
 
 
 def test_should_visit_descendent_order():
-    lc = list(RepositoryMining('test-repos/small_repo',
-                               order='reverse').traverse_commits())
+    lc = list(Repository('test-repos/small_repo',
+                         order='reverse').traverse_commits())
     assert len(lc) == 5
     assert lc[0].hash == 'da39b1326dbc2edfe518b90672734a08f3c13458'
     assert lc[1].hash == '1f99848edadfffa903b8ba1286a935f1b92b2845'
@@ -329,10 +328,10 @@ def test_should_visit_descendent_order():
 
 
 def test_should_visit_descendent_order_with_filters():
-    lc = list(RepositoryMining('test-repos/small_repo',
-                               from_commit='1f99848edadfffa903b8ba1286a935f1b92b2845',
-                               to_commit='6411e3096dd2070438a17b225f44475136e54e3a',
-                               order='reverse').traverse_commits())
+    lc = list(Repository('test-repos/small_repo',
+                         from_commit='1f99848edadfffa903b8ba1286a935f1b92b2845',
+                         to_commit='6411e3096dd2070438a17b225f44475136e54e3a',
+                         order='reverse').traverse_commits())
     assert len(lc) == 3
     assert lc[0].hash == '1f99848edadfffa903b8ba1286a935f1b92b2845'
     assert lc[1].hash == '09f6182cef737db02a085e1d018963c7a29bde5a'
@@ -340,10 +339,10 @@ def test_should_visit_descendent_order_with_filters():
 
 
 def test_should_visit_descendent_order_with_filters_reversed():
-    lc = list(RepositoryMining('test-repos/small_repo',
-                               from_commit='6411e3096dd2070438a17b225f44475136e54e3a',
-                               to_commit='1f99848edadfffa903b8ba1286a935f1b92b2845',
-                               order='reverse').traverse_commits())
+    lc = list(Repository('test-repos/small_repo',
+                         from_commit='6411e3096dd2070438a17b225f44475136e54e3a',
+                         to_commit='1f99848edadfffa903b8ba1286a935f1b92b2845',
+                         order='reverse').traverse_commits())
     assert len(lc) == 3
     assert lc[0].hash == '1f99848edadfffa903b8ba1286a935f1b92b2845'
     assert lc[1].hash == '09f6182cef737db02a085e1d018963c7a29bde5a'
@@ -351,13 +350,13 @@ def test_should_visit_descendent_order_with_filters_reversed():
 
 
 def test_include_refs():
-    commits_no_refs = list(RepositoryMining('test-repos/branches_not_merged/',
-                                            include_refs=False).traverse_commits())
+    commits_no_refs = list(Repository('test-repos/branches_not_merged/',
+                                      include_refs=False).traverse_commits())
     assert len(commits_no_refs) == 3
     commit_no_refs_hashes = [commit.hash for commit in commits_no_refs]
 
-    commits_with_refs = list(RepositoryMining('test-repos/branches_not_merged/',
-                                              include_refs=True).traverse_commits())
+    commits_with_refs = list(Repository('test-repos/branches_not_merged/',
+                                        include_refs=True).traverse_commits())
     assert len(commits_with_refs) == 6
     commit_with_refs_hashes = [commit.hash for commit in commits_with_refs]
 
@@ -376,14 +375,14 @@ def test_include_remotes():
     repo = Repo('test-repos/pydriller/')
 
     # Prove that a recent commit is not in 'test-repos/pydriller'
-    commits_before_fetch = [commit.hash for commit in RepositoryMining('test-repos/pydriller/').traverse_commits()]
+    commits_before_fetch = [commit.hash for commit in Repository('test-repos/pydriller/').traverse_commits()]
     assert '2fa0d8c57829b086c9372722115a89d11c9bdd35' not in commits_before_fetch
 
     # Fetch and re-assert that the commit is still not in the repo
     repo.git.fetch('--all')
-    commits_after_fetch = [commit.hash for commit in RepositoryMining('test-repos/pydriller/').traverse_commits()]
+    commits_after_fetch = [commit.hash for commit in Repository('test-repos/pydriller/').traverse_commits()]
     assert '2fa0d8c57829b086c9372722115a89d11c9bdd35' not in commits_after_fetch
 
     # Set include_remotes=True and assert that the commit is now found
-    commits_with_remotes = [commit.hash for commit in RepositoryMining('test-repos/pydriller/', include_remotes=True).traverse_commits()]
+    commits_with_remotes = [commit.hash for commit in Repository('test-repos/pydriller/', include_remotes=True).traverse_commits()]
     assert '2fa0d8c57829b086c9372722115a89d11c9bdd35' in commits_with_remotes
diff --git a/tests/integration/test_dates_and_timezones.py b/tests/integration/test_dates_and_timezones.py
index 4c98f7d..984cbed 100644
--- a/tests/integration/test_dates_and_timezones.py
+++ b/tests/integration/test_dates_and_timezones.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from pydriller.repository_mining import RepositoryMining
+from pydriller.repository import Repository
 from datetime import datetime, timezone, timedelta
 import logging
 
@@ -22,8 +22,8 @@ logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s',
 
 def test_one_timezone():
     lc = list(
-        RepositoryMining('test-repos/branches_merged',
-                         single='29e929fbc5dc6a2e9c620069b24e2a143af4285f').traverse_commits())
+        Repository('test-repos/branches_merged',
+                   single='29e929fbc5dc6a2e9c620069b24e2a143af4285f').traverse_commits())
 
     to_zone = timezone(timedelta(hours=2))
     dt = datetime(2016, 4, 4, 13, 21, 25, tzinfo=to_zone)
@@ -33,8 +33,8 @@ def test_one_timezone():
 
 def test_between_dates_reversed():
     lc = list(
-        RepositoryMining('test-repos/different_files',
-                         single='375de7a8275ecdc0b28dc8de2568f47241f443e9').traverse_commits())
+        Repository('test-repos/different_files',
+                   single='375de7a8275ecdc0b28dc8de2568f47241f443e9').traverse_commits())
 
     to_zone = timezone(timedelta(hours=-4))
     dt = datetime(2016, 10, 8, 17, 57, 49, tzinfo=to_zone)
diff --git a/tests/integration/test_ranges.py b/tests/integration/test_ranges.py
index 417ed64..50a7f7b 100644
--- a/tests/integration/test_ranges.py
+++ b/tests/integration/test_ranges.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from pydriller.repository_mining import RepositoryMining
+from pydriller.repository import Repository
 from datetime import datetime, timezone, timedelta
 import logging
 import pytest
@@ -31,27 +31,27 @@ dt3 = datetime(2018, 3, 27, 17, 20, 3, tzinfo=to_zone)
 @pytest.fixture
 def repository_mining_st(request):
     since, to = request.param
-    return list(RepositoryMining('test-repos/small_repo/', since=since, to=to).traverse_commits())
+    return list(Repository('test-repos/small_repo/', since=since, to=to).traverse_commits())
 
 
 @pytest.fixture
 def repository_mining_cc(request):
     from_commit, to_commit = request.param
-    return list(RepositoryMining('test-repos/small_repo/', from_commit=from_commit, to_commit=to_commit).traverse_commits())
+    return list(Repository('test-repos/small_repo/', from_commit=from_commit, to_commit=to_commit).traverse_commits())
 
 
 @pytest.fixture
 def repository_mining_tt(request):
     from_tag, to_tag = request.param
-    return list(RepositoryMining('test-repos/small_repo/', from_tag=from_tag, to_tag=to_tag).traverse_commits())
+    return list(Repository('test-repos/small_repo/', from_tag=from_tag, to_tag=to_tag).traverse_commits())
 
 
 @pytest.fixture
 def repository_mining_complex_tags(request):
     from_tag, to_tag = request.param
-    return list(RepositoryMining('test-repos/tags',
-                                 from_tag=from_tag,
-                                 to_tag=to_tag).traverse_commits())
+    return list(Repository('test-repos/tags',
+                           from_tag=from_tag,
+                           to_tag=to_tag).traverse_commits())
 
 
 @pytest.mark.parametrize('repository_mining_st,expected_commits', [
@@ -117,9 +117,9 @@ def test_from_and_to_commit(repository_mining_cc, expected_commits):
 
 
 def test_from_and_to_commit_with_merge_commit():
-    commits = RepositoryMining('test-repos/pydriller',
-                               from_commit="015f7144641a418f6a9fae4d024286ec17fd7ce8",
-                               to_commit="01d2f2fbeb6980cc5568825d008017ca8ca767d6").traverse_commits()
+    commits = Repository('test-repos/pydriller',
+                         from_commit="015f7144641a418f6a9fae4d024286ec17fd7ce8",
+                         to_commit="01d2f2fbeb6980cc5568825d008017ca8ca767d6").traverse_commits()
     assert len(list(commits)) == 3
 
 
@@ -152,40 +152,40 @@ def test_multiple_filters_exceptions():
     from_tag = 'v1.4'
 
     with pytest.raises(Exception):
-        for commit in RepositoryMining('test-repos/small_repo/',
-                                       since=dt1,
-                                       from_commit=from_commit
-                                       ).traverse_commits():
+        for commit in Repository('test-repos/small_repo/',
+                                 since=dt1,
+                                 from_commit=from_commit
+                                 ).traverse_commits():
             print(commit.hash)
 
     with pytest.raises(Exception):
-        for commit in RepositoryMining('test-repos/small_repo/',
-                                       since=dt1,
-                                       from_tag=from_tag).traverse_commits():
+        for commit in Repository('test-repos/small_repo/',
+                                 since=dt1,
+                                 from_tag=from_tag).traverse_commits():
             print(commit.hash)
 
     with pytest.raises(Exception):
-        for commit in RepositoryMining('test-repos/small_repo/',
-                                       from_commit=from_commit,
-                                       from_tag=from_tag).traverse_commits():
+        for commit in Repository('test-repos/small_repo/',
+                                 from_commit=from_commit,
+                                 from_tag=from_tag).traverse_commits():
             print(commit.hash)
 
     with pytest.raises(Exception):
-        for commit in RepositoryMining('test-repos/small_repo/',
-                                       to=dt1,
-                                       to_commit=from_commit
-                                       ).traverse_commits():
+        for commit in Repository('test-repos/small_repo/',
+                                 to=dt1,
+                                 to_commit=from_commit
+                                 ).traverse_commits():
             print(commit.hash)
 
     with pytest.raises(Exception):
-        for commit in RepositoryMining('test-repos/small_repo/',
-                                       to=dt1,
-                                       to_tag=from_tag).traverse_commits():
+        for commit in Repository('test-repos/small_repo/',
+                                 to=dt1,
+                                 to_tag=from_tag).traverse_commits():
             print(commit.hash)
 
     with pytest.raises(Exception):
-        for commit in RepositoryMining('test-repos/small_repo/',
-                                       single=from_commit,
-                                       to=dt1,
-                                       to_tag=from_tag).traverse_commits():
+        for commit in Repository('test-repos/small_repo/',
+                                 single=from_commit,
+                                 to=dt1,
+                                 to_tag=from_tag).traverse_commits():
             print(commit.hash)
diff --git a/tests/test_commit.py b/tests/test_commit.py
index d5270ee..8be3efb 100644
--- a/tests/test_commit.py
+++ b/tests/test_commit.py
@@ -11,12 +11,12 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-from pydriller.git_repository import GitRepository
+from pydriller.git import Git
 from pathlib import Path
 import pytest
 import logging
 
-from pydriller.domain.commit import Modification, ModificationType
+from pydriller.domain.commit import ModifiedFile, ModificationType
 
 logging.basicConfig(
     format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
@@ -24,13 +24,13 @@ logging.basicConfig(
 
 @pytest.fixture
 def repo(request):
-    gr = GitRepository(request.param)
+    gr = Git(request.param)
     yield gr
     gr.clear()
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_equal(repo: GitRepository):
+def test_equal(repo: Git):
     c1 = repo.get_commit('e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2')
     c2 = repo.get_commit(c1.parents[0])
     c3 = repo.get_commit('a4ece0762e797d2e2dcbd471115108dd6e05ff58')
@@ -46,13 +46,13 @@ def test_filename():
         'source_code': '',
         'source_code_before': ''
     }
-    m1 = Modification('dspadini/pydriller/myfile.py',
+    m1 = ModifiedFile('dspadini/pydriller/myfile.py',
                       'dspadini/pydriller/mynewfile.py',
                       ModificationType.ADD, diff_and_sc)
-    m3 = Modification('dspadini/pydriller/myfile.py',
+    m3 = ModifiedFile('dspadini/pydriller/myfile.py',
                       'dspadini/pydriller/mynewfile.py',
                       ModificationType.ADD, diff_and_sc)
-    m2 = Modification('dspadini/pydriller/myfile.py',
+    m2 = ModifiedFile('dspadini/pydriller/myfile.py',
                       None,
                       ModificationType.ADD, diff_and_sc)
 
@@ -72,7 +72,7 @@ def test_metrics_python():
         'source_code_before': sc
     }
 
-    m1 = Modification('test-repos/lizard/git_repository.py',
+    m1 = ModifiedFile('test-repos/lizard/git_repository.py',
                       "test-repos/lizard/git_repository.py",
                       ModificationType.MODIFY, diff_and_sc)
 
@@ -85,54 +85,54 @@ def test_metrics_python():
 
 def test_changed_methods():
 
-    gr = GitRepository("test-repos/diff")
+    gr = Git("test-repos/diff")
 
     # add a new method
     mod = gr.get_commit(
-        'ea95227e0fd128aa69c7ab6a8ac485f72251b3ed').modifications[0]
+        'ea95227e0fd128aa69c7ab6a8ac485f72251b3ed').modified_files[0]
     assert len(mod.changed_methods) == 1
     assert mod.changed_methods[0].name == 'GitRepository::singleProjectThirdMethod'
 
     # add 2 new methods
     mod = gr.get_commit(
-        'd8eb8e80b671246a43c98d97b05f6d1c5ada14fb').modifications[0]
+        'd8eb8e80b671246a43c98d97b05f6d1c5ada14fb').modified_files[0]
     assert len(mod.changed_methods) == 2
 
     # remove one method
     mod = gr.get_commit(
-        '0c8f9fdec926785198b399a2c49adb5884aa952c').modifications[0]
+        '0c8f9fdec926785198b399a2c49adb5884aa952c').modified_files[0]
     assert len(mod.changed_methods) == 1
 
     # add and remove one one method at different locations
     mod = gr.get_commit(
-        'd8bb142c5616041b71cbfaa11eeb768d9a1a296e').modifications[0]
+        'd8bb142c5616041b71cbfaa11eeb768d9a1a296e').modified_files[0]
     assert len(mod.changed_methods) == 2
 
     # add and remove one one method at the same location
     # this is equivalent to replacing a method - although we expect 2 methods
     mod = gr.get_commit(
-        '9e9473d5ca310b7663e9df93c402302b6b7f24aa').modifications[0]
+        '9e9473d5ca310b7663e9df93c402302b6b7f24aa').modified_files[0]
     assert len(mod.changed_methods) == 2
 
     # update a method
     mod = gr.get_commit(
-        'b267a14e0503fdac36d280422f16360d1f661f12').modifications[0]
+        'b267a14e0503fdac36d280422f16360d1f661f12').modified_files[0]
     assert len(mod.changed_methods) == 1
 
     # update and add a new method
     mod = gr.get_commit(
-        '2489099dfd90edb99ddc2c82b62524b66c07c687').modifications[0]
+        '2489099dfd90edb99ddc2c82b62524b66c07c687').modified_files[0]
     assert len(mod.changed_methods) == 2
 
     # update and delete methods
     mod = gr.get_commit(
-        '5aebeb30e0238543a93e5bed806639481460cd9a').modifications[0]
+        '5aebeb30e0238543a93e5bed806639481460cd9a').modified_files[0]
     assert len(mod.changed_methods) == 2
 
     # delete 3 methods (test cleanup - revert the test file to its
     # initial set of methods)
     mod = gr.get_commit(
-        '9f6ddc2aac740a257af59a76860590cb8a84c77b').modifications[0]
+        '9f6ddc2aac740a257af59a76860590cb8a84c77b').modified_files[0]
     assert len(mod.changed_methods) == 3
 
 
@@ -146,7 +146,7 @@ def test_metrics_cpp():
         'source_code_before': sc
     }
 
-    m1 = Modification('test-repos/lizard/FileCPP.cpp',
+    m1 = ModifiedFile('test-repos/lizard/FileCPP.cpp',
                       "test-repos/lizard/FileCPP.cpp",
                       ModificationType.MODIFY, diff_and_sc)
 
@@ -167,7 +167,7 @@ def test_metrics_java():
         'source_code_before': sc
     }
 
-    m1 = Modification('test-repos/lizard/FileJava.java',
+    m1 = ModifiedFile('test-repos/lizard/FileJava.java',
                       "test-repos/lizard/FileJava.java",
                       ModificationType.MODIFY, diff_and_sc)
 
@@ -187,7 +187,7 @@ def test_metrics_not_supported_file():
         'source_code_before': sc
     }
 
-    m1 = Modification('test-repos/lizard/NotSupported.pdf',
+    m1 = ModifiedFile('test-repos/lizard/NotSupported.pdf',
                       "test-repos/lizard/NotSupported.pdf",
                       ModificationType.MODIFY, diff_and_sc)
 
@@ -195,10 +195,10 @@ def test_metrics_not_supported_file():
 
 
 @pytest.mark.parametrize('repo', ['test-repos/files_in_directories'], indirect=True)
-def test_filepahs(repo: GitRepository):
+def test_filepahs(repo: Git):
     c = repo.get_commit('f0f8aea2db50ed9f16332d86af3629ff7780583e')
 
-    mod0 = c.modifications[0]
+    mod0 = c.modified_files[0]
 
     assert mod0.filename == 'a.java'
     assert mod0.new_path == str(Path('dir2/a.java'))
@@ -206,39 +206,39 @@ def test_filepahs(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/files_in_directories'], indirect=True)
-def test_projectname(repo: GitRepository):
+def test_projectname(repo: Git):
     c = repo.get_commit('f0f8aea2db50ed9f16332d86af3629ff7780583e')
 
     assert c.project_name == 'files_in_directories'
 
 
 @pytest.mark.parametrize('repo', ['test-repos/files_in_directories'], indirect=True)
-def test_projectpath(repo: GitRepository):
+def test_projectpath(repo: Git):
     c = repo.get_commit('f0f8aea2db50ed9f16332d86af3629ff7780583e')
 
     assert c.project_path.endswith('files_in_directories') is True
 
 
 @pytest.mark.parametrize('repo', ['test-repos/unknown_modification'], indirect=True)
-def test_modification_type_unknown(repo: GitRepository):
+def test_modification_type_unknown(repo: Git):
     c = repo.get_commit('1734d6da01378bad3aade12b52bb4aa8954835dc')
 
-    mod0 = c.modifications[0]
+    mod0 = c.modified_files[0]
 
     assert mod0.change_type.name == 'UNKNOWN'
 
 
 @pytest.mark.parametrize('repo', ['test-repos/empty_modifications'], indirect=True)
-def test_modification_with_more_parents(repo: GitRepository):
+def test_modification_with_more_parents(repo: Git):
     c = repo.get_commit('ce6bcd987a6a53cc55da7cef9f8bb128adf68741')
-    assert len(c.modifications) == 0
+    assert len(c.modified_files) == 0
 
     c = repo.get_commit('1b03d13c816f576eb82a8c3e935fbcacff6c2e8d')
-    assert len(c.modifications) == 0
+    assert len(c.modified_files) == 0
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo'], indirect=True)
-def test_eq_commit(repo: GitRepository):
+def test_eq_commit(repo: Git):
     c1 = repo.get_commit('6411e3096dd2070438a17b225f44475136e54e3a')
     c2 = repo.get_commit('09f6182cef737db02a085e1d018963c7a29bde5a')
     c3 = repo.get_commit('6411e3096dd2070438a17b225f44475136e54e3a')
@@ -252,13 +252,10 @@ def test_eq_commit(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_eq_modifications(repo: GitRepository):
-    m1 = repo.get_commit('e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2'
-                         '').modifications[0]
-    m2 = repo.get_commit('e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2'
-                         '').modifications[0]
-    m3 = repo.get_commit('a4ece0762e797d2e2dcbd471115108dd6e05ff58'
-                         '').modifications[0]
+def test_eq_modifications(repo: Git):
+    m1 = repo.get_commit('e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2').modified_files[0]
+    m2 = repo.get_commit('e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2').modified_files[0]
+    m3 = repo.get_commit('a4ece0762e797d2e2dcbd471115108dd6e05ff58').modified_files[0]
     c1 = repo.get_commit('a4ece0762e797d2e2dcbd471115108dd6e05ff58')
 
     assert m1 == m2
@@ -268,38 +265,32 @@ def test_eq_modifications(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_tzoffset_minus_hours(repo: GitRepository):
-    tz1 = repo.get_commit(
-        'e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2').author_timezone
-    tz2 = repo.get_commit(
-        'e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2').committer_timezone
+def test_tzoffset_minus_hours(repo: Git):
+    tz1 = repo.get_commit('e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2').author_timezone
+    tz2 = repo.get_commit('e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2').committer_timezone
     assert tz1 == 10800  # -3 hours
     assert tz2 == 10800  # -3 hours
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo'], indirect=True)
-def test_tzoffset_plus_hours(repo: GitRepository):
-    tz1 = repo.get_commit(
-        'da39b1326dbc2edfe518b90672734a08f3c13458').author_timezone
-    tz2 = repo.get_commit(
-        'da39b1326dbc2edfe518b90672734a08f3c13458').committer_timezone
+def test_tzoffset_plus_hours(repo: Git):
+    tz1 = repo.get_commit('da39b1326dbc2edfe518b90672734a08f3c13458').author_timezone
+    tz2 = repo.get_commit('da39b1326dbc2edfe518b90672734a08f3c13458').committer_timezone
     assert tz1 == -7200  # +2 hours
     assert tz2 == -7200  # +2 hours
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_source_code_before(repo: GitRepository):
-    m1 = repo.get_commit('ffccf1e7497eb8136fd66ed5e42bef29677c4b71'
-                         '').modifications[0]
+def test_source_code_before(repo: Git):
+    m1 = repo.get_commit('ffccf1e7497eb8136fd66ed5e42bef29677c4b71').modified_files[0]
 
     assert m1.source_code is None
     assert m1.source_code_before is not None
 
 
 @pytest.mark.parametrize('repo', ['test-repos/source_code_before_commit'], indirect=True)
-def test_source_code_before_complete(repo: GitRepository):
-    m1 = repo.get_commit(
-        'ca1f75455f064410360bc56218d0418221cf9484').modifications[0]
+def test_source_code_before_complete(repo: Git):
+    m1 = repo.get_commit('ca1f75455f064410360bc56218d0418221cf9484').modified_files[0]
 
     with open('test-repos/source_code_before_commit/'
               'sc_A_ca1f75455f064410360bc56218d0418221cf9484.txt') as f:
@@ -314,22 +305,20 @@ def test_source_code_before_complete(repo: GitRepository):
             'sc_A_022ebf5fba835c6d95e99eaccc2d85b3db5a2ec0.txt') as f:
         sc = f.read()
 
-    m1 = repo.get_commit(
-        '022ebf5fba835c6d95e99eaccc2d85b3db5a2ec0').modifications[0]
+    m1 = repo.get_commit('022ebf5fba835c6d95e99eaccc2d85b3db5a2ec0').modified_files[0]
 
     assert m1.source_code == sc
     assert m1.source_code_before == old_sc
 
     old_sc = sc
-    m1 = repo.get_commit(
-        'ecd6780457835a2fc85c532338a29f2c98a6cfeb').modifications[0]
+    m1 = repo.get_commit('ecd6780457835a2fc85c532338a29f2c98a6cfeb').modified_files[0]
 
     assert m1.source_code is None
     assert m1.source_code_before == old_sc
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo'], indirect=True)
-def test_shortstats_all_additions(repo: GitRepository):
+def test_shortstats_all_additions(repo: Git):
     c1 = repo.get_commit('a88c84ddf42066611e76e6cb690144e5357d132c')
 
     assert c1.insertions == 191
@@ -339,7 +328,7 @@ def test_shortstats_all_additions(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo'], indirect=True)
-def test_shortstats_all_deletions(repo: GitRepository):
+def test_shortstats_all_deletions(repo: Git):
     c1 = repo.get_commit('6411e3096dd2070438a17b225f44475136e54e3a')
 
     assert c1.insertions == 0
@@ -349,7 +338,7 @@ def test_shortstats_all_deletions(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo'], indirect=True)
-def test_shortstats_rename(repo: GitRepository):
+def test_shortstats_rename(repo: Git):
     c1 = repo.get_commit('da39b1326dbc2edfe518b90672734a08f3c13458')
 
     assert c1.insertions == 0
@@ -359,7 +348,7 @@ def test_shortstats_rename(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_shortstats_add_and_del(repo: GitRepository):
+def test_shortstats_add_and_del(repo: Git):
     c1 = repo.get_commit('e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2')
 
     assert c1.insertions == 1
diff --git a/tests/test_diff.py b/tests/test_diff.py
index 0c50343..aa33557 100644
--- a/tests/test_diff.py
+++ b/tests/test_diff.py
@@ -13,21 +13,21 @@
 # limitations under the License.
 import pytest
 
-from pydriller import GitRepository, Modification
+from pydriller import Git, ModifiedFile
 
 
 @pytest.fixture()
 def modification(request):
     path, commit = request.param
-    gr = GitRepository(path)
-    yield gr.get_commit(commit).modifications[0]
+    gr = Git(path)
+    yield gr.get_commit(commit).modified_files[0]
     gr.clear()
 
 
 @pytest.mark.parametrize('modification',
                          [("test-repos/diff", "9a985d4a12a3a12f009ef39750fd9b2187b766d1")],
                          indirect=True)
-def test_extract_line_number_and_content(modification: Modification):
+def test_extract_line_number_and_content(modification: ModifiedFile):
     added = modification.diff_parsed['added']
     deleted = modification.diff_parsed['deleted']
 
@@ -41,7 +41,7 @@ def test_extract_line_number_and_content(modification: Modification):
 @pytest.mark.parametrize('modification',
                          [("test-repos/diff", "f45ee2f8976d5f018a1e4ec83eb4556a3df8b0a5")],
                          indirect=True)
-def test_additions(modification: Modification):
+def test_additions(modification: ModifiedFile):
     added = modification.diff_parsed['added']
     deleted = modification.diff_parsed['deleted']
 
@@ -57,7 +57,7 @@ def test_additions(modification: Modification):
 @pytest.mark.parametrize('modification',
                          [("test-repos/diff", "147c7ce9f725a0e259d63f0bf4e6c8ac085ff8c8")],
                          indirect=True)
-def test_deletions(modification: Modification):
+def test_deletions(modification: ModifiedFile):
     added = modification.diff_parsed['added']
     deleted = modification.diff_parsed['deleted']
 
@@ -80,7 +80,7 @@ def test_deletions(modification: Modification):
 @pytest.mark.parametrize('modification',
                          [("test-repos/no_newline", "52a78c1ee5d100528eccba0a3d67371dbd22d898")],
                          indirect=True)
-def test_diff_no_newline(modification: Modification):
+def test_diff_no_newline(modification: ModifiedFile):
     """
     If a file ends without a newline git represents this with the additional line
         \\ No newline at end of file
diff --git a/tests/test_dmm.py b/tests/test_dmm.py
index 872a921..60fd790 100644
--- a/tests/test_dmm.py
+++ b/tests/test_dmm.py
@@ -15,7 +15,7 @@
 import logging
 import pytest
 
-from pydriller.git_repository import GitRepository
+from pydriller.git import Git
 from pydriller.domain.commit import Commit, DMMProperty
 
 
@@ -26,7 +26,7 @@ logging.basicConfig(
 @pytest.fixture()
 def repo():
     path = "test-repos/dmm-test-repo"
-    gr = GitRepository(path)
+    gr = Git(path)
     yield gr
     gr.clear()
 
@@ -95,7 +95,7 @@ UNIT_INTERFACING_TEST_DATA = [
 ]
 
 
-def commit_by_msg(repo: GitRepository, msg: str) -> Commit:
+def commit_by_msg(repo: Git, msg: str) -> Commit:
     for commit in repo.get_list_commits():
         if commit.msg == msg:
             return commit
@@ -103,56 +103,56 @@ def commit_by_msg(repo: GitRepository, msg: str) -> Commit:
 
 
 @pytest.mark.parametrize('msg,dmm', UNIT_SIZE_TEST_DATA)
-def test_dmm_unit_size(repo: GitRepository, msg: str, dmm: float):
+def test_dmm_unit_size(repo: Git, msg: str, dmm: float):
     commit = commit_by_msg(repo, msg)
     assert commit.dmm_unit_size == dmm
 
 
 @pytest.mark.parametrize('msg,dmm', UNIT_COMPLEXITY_TEST_DATA)
-def test_dmm_unit_complexity(repo: GitRepository, msg: str, dmm: float):
+def test_dmm_unit_complexity(repo: Git, msg: str, dmm: float):
     commit = commit_by_msg(repo, msg)
     assert commit.dmm_unit_complexity == dmm
 
 
 @pytest.mark.parametrize('msg,dmm', UNIT_INTERFACING_TEST_DATA)
-def test_dmm_unit_interfacing(repo: GitRepository, msg: str, dmm: float):
+def test_dmm_unit_interfacing(repo: Git, msg: str, dmm: float):
     commit = commit_by_msg(repo, msg)
     assert commit.dmm_unit_interfacing == dmm
 
 
-def test_unsupported_language(repo: GitRepository):
+def test_unsupported_language(repo: Git):
     # Add .md file that cannot be analyzed by Lizard
     commit = commit_by_msg(repo, 'Offer README explaining the repo purpose')
     assert commit.dmm_unit_size is None
 
 
-def test_mixin_unsupported_language(repo: GitRepository):
+def test_mixin_unsupported_language(repo: Git):
     # Add .txt file and update (comments in) .java files
     commit = commit_by_msg(repo, 'Release under Apache 2 license')
     assert commit.dmm_unit_size is None
 
 
-def test_delta_profile_modification(repo: GitRepository):
+def test_delta_profile_modification(repo: Git):
     commit = commit_by_msg(repo, 'Increase unit size to risky')
-    mod = commit.modifications[0]
+    mod = commit.modified_files[0]
     assert mod._delta_risk_profile(DMMProperty.UNIT_SIZE) == (-15, 16)
 
 
-def test_delta_profile_commit(repo: GitRepository):
+def test_delta_profile_commit(repo: Git):
     commit = commit_by_msg(repo, 'Increase in one, decrease in other file')
 
-    m0 = commit.modifications[0]
+    m0 = commit.modified_files[0]
     assert m0._delta_risk_profile(DMMProperty.UNIT_SIZE) == (0, 1)
-    m1 = commit.modifications[1]
+    m1 = commit.modified_files[1]
     assert m1._delta_risk_profile(DMMProperty.UNIT_SIZE) == (3, 0)
 
     assert commit._delta_risk_profile(DMMProperty.UNIT_SIZE) == (3, 1)
 
 
-def test_supported_languages(repo: GitRepository):
+def test_supported_languages(repo: Git):
     # Add .md file that cannot be analyzed by Lizard
     commit = commit_by_msg(repo, 'Offer README explaining the repo purpose')
-    mod = commit.modifications[0]
+    mod = commit.modified_files[0]
     assert not mod.language_supported
 
 
diff --git a/tests/test_git_repository.py b/tests/test_git_repository.py
index a670e6d..5807a96 100644
--- a/tests/test_git_repository.py
+++ b/tests/test_git_repository.py
@@ -15,26 +15,26 @@ from datetime import datetime, timezone, timedelta
 from pathlib import Path
 
 import pytest
-from git import Git
+from git import Git as PyGit
 
 from pydriller.domain.commit import ModificationType
-from pydriller.git_repository import GitRepository
+from pydriller.git import Git
 
 
 @pytest.fixture
 def repo(request):
-    gr = GitRepository(request.param)
+    gr = Git(request.param)
     yield gr
     gr.clear()
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo/'], indirect=True)
-def test_projectname(repo: GitRepository):
+def test_projectname(repo: Git):
     assert repo.project_name == "small_repo"
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo/'], indirect=True)
-def test_get_head(repo: GitRepository):
+def test_get_head(repo: Git):
     assert repo is not None
     cs = repo.get_head()
     assert cs is not None
@@ -44,7 +44,7 @@ def test_get_head(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo/'], indirect=True)
-def test_list_commits(repo: GitRepository):
+def test_list_commits(repo: Git):
     change_sets = list(repo.get_list_commits())
 
     list_commits = {'a88c84ddf42066611e76e6cb690144e5357d132c',
@@ -60,7 +60,7 @@ def test_list_commits(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo/'], indirect=True)
-def test_get_commit(repo: GitRepository):
+def test_get_commit(repo: Git):
     c = repo.get_commit('09f6182cef737db02a085e1d018963c7a29bde5a')
     to_zone = timezone(timedelta(hours=1))
 
@@ -69,7 +69,7 @@ def test_get_commit(repo: GitRepository):
     assert c.committer.name == 'ishepard'
     assert c.author_date.timestamp() == datetime(2018, 3, 22, 10, 42, 3,
                                                  tzinfo=to_zone).timestamp()
-    assert len(c.modifications) == 1
+    assert len(c.modified_files) == 1
     assert c.msg == 'Ooops file2'
     assert c.in_main_branch is True
     assert c.insertions == 4
@@ -79,13 +79,13 @@ def test_get_commit(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/detached_head/'], indirect=True)
-def test_detached_head(repo: GitRepository):
+def test_detached_head(repo: Git):
     c = repo.get_commit('56c5ef54d9d16d2b2255412f9479830b5b97cb99')
     assert c.in_main_branch is False
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo/'], indirect=True)
-def test_get_first_commit(repo: GitRepository):
+def test_get_first_commit(repo: Git):
     c = repo.get_commit('a88c84ddf42066611e76e6cb690144e5357d132c')
     to_zone = timezone(timedelta(hours=1))
 
@@ -96,16 +96,16 @@ def test_get_first_commit(repo: GitRepository):
                                                  tzinfo=to_zone).timestamp()
     assert c.committer_date.timestamp() == datetime(2018, 3, 22, 10, 41, 11,
                                                     tzinfo=to_zone).timestamp()
-    assert len(c.modifications) == 2
+    assert len(c.modified_files) == 2
     assert c.msg == 'First commit adding 2 files'
     assert c.in_main_branch is True
 
-    assert c.modifications[0].change_type == ModificationType.ADD
-    assert c.modifications[1].change_type == ModificationType.ADD
+    assert c.modified_files[0].change_type == ModificationType.ADD
+    assert c.modified_files[1].change_type == ModificationType.ADD
 
 
 @pytest.mark.parametrize('repo', ['test-repos/files/'], indirect=True)
-def test_files(repo: GitRepository):
+def test_files(repo: Git):
     files = repo.files()
     assert len(files) == 8
 
@@ -123,12 +123,12 @@ def test_files(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo/'], indirect=True)
-def test_total_commits(repo: GitRepository):
+def test_total_commits(repo: Git):
     assert repo.total_commits() == 5
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo/'], indirect=True)
-def test_get_commit_from_tag(repo: GitRepository):
+def test_get_commit_from_tag(repo: Git):
     commit = repo.get_commit_from_tag('v1.4')
 
     assert commit.hash == '09f6182cef737db02a085e1d018963c7a29bde5a'
@@ -137,7 +137,7 @@ def test_get_commit_from_tag(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_list_files_in_commit(repo: GitRepository):
+def test_list_files_in_commit(repo: Git):
     repo.checkout('a7053a4dcd627f5f4f213dc9aa002eb1caf926f8')
     files1 = repo.files()
     assert len(files1) == 3
@@ -155,7 +155,7 @@ def test_list_files_in_commit(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_checkout_consecutive_commits(repo: GitRepository):
+def test_checkout_consecutive_commits(repo: Git):
     repo.checkout('a7053a4dcd627f5f4f213dc9aa002eb1caf926f8')
     repo.checkout('f0dd1308bd904a9b108a6a40865166ee962af3d4')
     repo.checkout('9e71dd5726d775fb4a5f08506a539216e878adbb')
@@ -165,7 +165,7 @@ def test_checkout_consecutive_commits(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/branches_without_files'], indirect=True)
-def test_checkout_with_commit_not_fully_merged_to_master(repo: GitRepository):
+def test_checkout_with_commit_not_fully_merged_to_master(repo: Git):
     repo.checkout('developing')
     files1 = repo.files()
     assert len(files1) == 2
@@ -182,7 +182,7 @@ def test_checkout_with_commit_not_fully_merged_to_master(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_get_all_commits(repo: GitRepository):
+def test_get_all_commits(repo: Git):
     change_sets = list(repo.get_list_commits())
 
     assert len(change_sets) == 13
@@ -191,7 +191,7 @@ def test_get_all_commits(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_branches_from_commit(repo: GitRepository):
+def test_branches_from_commit(repo: Git):
     commit = repo.get_commit('a997e9d400f742003dea601bb05a9315d14d1124')
 
     assert len(commit.branches) == 1
@@ -204,7 +204,7 @@ def test_branches_from_commit(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/branches_not_merged'], indirect=True)
-def test_other_branches_with_merge(repo: GitRepository):
+def test_other_branches_with_merge(repo: Git):
     commit = repo.get_commit('7203c0b8220dcc7a59614bc7549799cd203ac072')
     assert commit.in_main_branch is False
 
@@ -219,12 +219,12 @@ def test_other_branches_with_merge(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/branches_merged'], indirect=True)
-def test_commit_in_master_branch(repo: GitRepository):
+def test_commit_in_master_branch(repo: Git):
     assert repo.get_head().hash == '29e929fbc5dc6a2e9c620069b24e2a143af4285f'
 
     repo.checkout('8986af2a679759e5a15794f6d56e6d46c3f302f1')
 
-    git_to_change_head = GitRepository('test-repos/branches_merged')
+    git_to_change_head = Git('test-repos/branches_merged')
     commit = git_to_change_head.get_commit('8169f76a3d7add54b4fc7bca7160d1f1eede6eda')
     assert commit.in_main_branch is False
 
@@ -233,22 +233,22 @@ def test_commit_in_master_branch(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_should_detail_a_commit(repo: GitRepository):
+def test_should_detail_a_commit(repo: Git):
     commit = repo.get_commit('866e997a9e44cb4ddd9e00efe49361420aff2559')
 
     assert commit.author.name == "Maurício Aniche"
     assert commit.author.email == "mauricioaniche@gmail.com"
 
     assert commit.msg == "Matricula adicionada"
-    assert len(commit.modifications) == 1
+    assert len(commit.modified_files) == 1
 
-    assert commit.modifications[0].new_path == "Matricula.java"
-    assert commit.modifications[0].diff.startswith("@@ -0,0 +1,62 @@\n+package model;") is True
-    assert commit.modifications[0].source_code.startswith("package model;") is True
+    assert commit.modified_files[0].new_path == "Matricula.java"
+    assert commit.modified_files[0].diff.startswith("@@ -0,0 +1,62 @@\n+package model;") is True
+    assert commit.modified_files[0].source_code.startswith("package model;") is True
 
 
 @pytest.mark.parametrize('repo', ['test-repos/branches_merged'], indirect=True)
-def test_merge_commits(repo: GitRepository):
+def test_merge_commits(repo: Git):
     commit = repo.get_commit("168b3aab057ed61a769acf336a4ef5e64f76c9fd")
     assert commit.merge is False
 
@@ -260,60 +260,60 @@ def test_merge_commits(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_number_of_modifications(repo: GitRepository):
+def test_number_of_modified_files(repo: Git):
     commit = repo.get_commit('866e997a9e44cb4ddd9e00efe49361420aff2559')
-    assert commit.modifications[0].added == 62
-    assert commit.modifications[0].removed == 0
+    assert commit.modified_files[0].added_lines == 62
+    assert commit.modified_files[0].deleted_lines == 0
 
     commit = repo.get_commit('d11dd6734ff4e60cac3a7b58d9267f138c9e05c7')
-    assert commit.modifications[0].added == 1
-    assert commit.modifications[0].removed == 1
+    assert commit.modified_files[0].added_lines == 1
+    assert commit.modified_files[0].deleted_lines == 1
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_modification_status(repo: GitRepository):
+def test_modification_status(repo: Git):
     commit = repo.get_commit('866e997a9e44cb4ddd9e00efe49361420aff2559')
-    assert commit.modifications[0].change_type == ModificationType.ADD
-    assert commit.modifications[0].old_path is None
+    assert commit.modified_files[0].change_type == ModificationType.ADD
+    assert commit.modified_files[0].old_path is None
 
     commit = repo.get_commit('57dbd017d1a744b949e7ca0b1c1a3b3dd4c1cbc1')
-    assert commit.modifications[0].change_type == ModificationType.MODIFY
-    assert commit.modifications[0].new_path == commit.modifications[0].old_path
+    assert commit.modified_files[0].change_type == ModificationType.MODIFY
+    assert commit.modified_files[0].new_path == commit.modified_files[0].old_path
 
     commit = repo.get_commit('ffccf1e7497eb8136fd66ed5e42bef29677c4b71')
-    assert commit.modifications[0].change_type == ModificationType.DELETE
-    assert commit.modifications[0].new_path is None
+    assert commit.modified_files[0].change_type == ModificationType.DELETE
+    assert commit.modified_files[0].new_path is None
 
 
 @pytest.mark.parametrize('repo', ['test-repos/two_modifications/'], indirect=True)
-def test_diffs(repo: GitRepository):
+def test_diffs(repo: Git):
     commit = repo.get_commit('93b4b18673ca6fb5d563bbf930c45cd1198e979b')
 
-    assert len(commit.modifications) == 2
+    assert len(commit.modified_files) == 2
 
-    for mod in commit.modifications:
+    for mod in commit.modified_files:
         if mod.filename == 'file4.java':
-            assert mod.removed == 8
-            assert mod.added == 0
+            assert mod.deleted_lines == 8
+            assert mod.added_lines == 0
 
         if mod.filename == 'file2.java':
-            assert mod.removed == 12
-            assert mod.added == 0
+            assert mod.deleted_lines == 12
+            assert mod.added_lines == 0
 
 
 @pytest.mark.parametrize('repo', ['test-repos/complex_repo'], indirect=True)
-def test_detail_rename(repo: GitRepository):
+def test_detail_rename(repo: Git):
     commit = repo.get_commit('f0dd1308bd904a9b108a6a40865166ee962af3d4')
 
     assert commit.author.name == "Maurício Aniche"
     assert commit.author.email == "mauricioaniche@gmail.com"
 
-    assert commit.modifications[0].new_path == "Matricula.javax"
-    assert commit.modifications[0].old_path == "Matricula.java"
+    assert commit.modified_files[0].new_path == "Matricula.javax"
+    assert commit.modified_files[0].old_path == "Matricula.java"
 
 
 @pytest.mark.parametrize('repo', ['test-repos/branches_merged'], indirect=True)
-def test_parent_commits(repo: GitRepository):
+def test_parent_commits(repo: Git):
     merge_commit = repo.get_commit('29e929fbc5dc6a2e9c620069b24e2a143af4285f')
     assert len(merge_commit.parents) == 2
     assert '8986af2a679759e5a15794f6d56e6d46c3f302f1' in merge_commit.parents
@@ -325,7 +325,7 @@ def test_parent_commits(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/tags'], indirect=True)
-def test_tags(repo: GitRepository):
+def test_tags(repo: Git):
     commit = repo.get_commit_from_tag('tag1')
     assert commit.hash == '6bb9e2c6a8080e6b5b34e6e316c894b2ddbf7fcd'
 
@@ -337,7 +337,7 @@ def test_tags(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_simple(repo: GitRepository):
+def test_get_commits_last_modified_lines_simple(repo: Git):
     buggy_commits = repo.get_commits_last_modified_lines(repo.get_commit('e6d3b38a9ef683e8184eac10a0471075c2808bbd'))
 
     assert len(buggy_commits) == 1
@@ -346,7 +346,7 @@ def test_get_commits_last_modified_lines_simple(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_multiple(repo: GitRepository):
+def test_get_commits_last_modified_lines_multiple(repo: Git):
     buggy_commits = repo.get_commits_last_modified_lines(repo.get_commit('9942ee9dcdd1103e5808d544a84e6bc8cade0e54'))
 
     assert len(buggy_commits) == 1
@@ -359,7 +359,7 @@ def test_get_commits_last_modified_lines_multiple(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_rename_and_fix(repo: GitRepository):
+def test_get_commits_last_modified_lines_rename_and_fix(repo: Git):
     buggy_commits = repo.get_commits_last_modified_lines(repo.get_commit('4e287ab8e6dba110219404fb8a43993f3dda674c'))
 
     assert len(buggy_commits) == 1
@@ -368,7 +368,7 @@ def test_get_commits_last_modified_lines_rename_and_fix(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_rename(repo: GitRepository):
+def test_get_commits_last_modified_lines_rename(repo: Git):
     buggy_commits = repo.get_commits_last_modified_lines(repo.get_commit('2f2d0dec7cd06de4c2ed11ed010727a54af8ebf8'))
 
     assert len(buggy_commits) == 1
@@ -377,7 +377,7 @@ def test_get_commits_last_modified_lines_rename(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_useless_lines(repo: GitRepository):
+def test_get_commits_last_modified_lines_useless_lines(repo: Git):
     buggy_commits = repo.get_commits_last_modified_lines(repo.get_commit('3bc7295c16b7dfc15d5f82eb6962a2774e1b8420'))
     assert len(buggy_commits) == 1
     assert 'c7fc2e870ce03b0b8dc29ed0eeb26d14e235ea3b' in buggy_commits[
@@ -385,16 +385,16 @@ def test_get_commits_last_modified_lines_useless_lines(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_useless_lines2(repo: GitRepository):
+def test_get_commits_last_modified_lines_useless_lines2(repo: Git):
     buggy_commits = repo.get_commits_last_modified_lines(repo.get_commit('4155c421ee5cbb3c34feee7b68aa78a2ee1bbeae'))
     assert len(buggy_commits) == 0
 
 
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_for_single_file(repo: GitRepository):
+def test_get_commits_last_modified_lines_for_single_file(repo: Git):
     commit = repo.get_commit('0f726924f96621e4965039123098ba83e39ffba6')
     buggy_commits = None
-    for mod in commit.modifications:
+    for mod in commit.modified_files:
         if mod.filename == 'A.java':
             buggy_commits = repo.get_commits_last_modified_lines(commit, mod)
 
@@ -405,7 +405,7 @@ def test_get_commits_last_modified_lines_for_single_file(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_with_more_modification(repo: GitRepository):
+def test_get_commits_last_modified_lines_with_more_modification(repo: Git):
     buggy_commits = repo.get_commits_last_modified_lines(repo.get_commit('c7002fb321a8ba32a28fac200538f7c2ba76f175'))
     assert len(buggy_commits) == 1
     assert '5cb9e9ae44a0949ec91d06a955975289be766f34' in buggy_commits[
@@ -413,7 +413,7 @@ def test_get_commits_last_modified_lines_with_more_modification(repo: GitReposit
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo/'], indirect=True)
-def test_get_commits_modified_file(repo: GitRepository):
+def test_get_commits_modified_file(repo: Git):
     commits = repo.get_commits_modified_file('file2.java')
 
     assert len(commits) == 3
@@ -423,14 +423,14 @@ def test_get_commits_modified_file(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/small_repo/'], indirect=True)
-def test_get_commits_modified_file_missing_file(repo: GitRepository):
+def test_get_commits_modified_file_missing_file(repo: Git):
     commits = repo.get_commits_modified_file('non-existing-file.java')
 
     assert len(commits) == 0
 
 
 @pytest.mark.parametrize('repo', ['test-repos/tags'], indirect=True)
-def test_get_tagged_commits(repo: GitRepository):
+def test_get_tagged_commits(repo: Git):
     tagged_commits = repo.get_tagged_commits()
 
     assert len(tagged_commits) == 3
@@ -440,14 +440,14 @@ def test_get_tagged_commits(repo: GitRepository):
 
 
 @pytest.mark.parametrize('repo', ['test-repos/different_files'], indirect=True)
-def test_get_tagged_commits_wo_tags(repo: GitRepository):
+def test_get_tagged_commits_wo_tags(repo: Git):
     tagged_commits = repo.get_tagged_commits()
 
     assert len(tagged_commits) == 0
 
 
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_hyper_blame(repo: GitRepository):
+def test_get_commits_last_modified_lines_hyper_blame(repo: Git):
     buggy_commits = repo.get_commits_last_modified_lines(repo.get_commit(
         'e6d3b38a9ef683e8184eac10a0471075c2808bbd'))
 
@@ -456,10 +456,10 @@ def test_get_commits_last_modified_lines_hyper_blame(repo: GitRepository):
         'B.java']
 
 
-@pytest.mark.skipif(Git().version_info < (2, 23),
+@pytest.mark.skipif(PyGit().version_info < (2, 23),
                     reason="requires 2.23 or higher")
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_hyper_blame_unblamable(tmp_path, repo: GitRepository):
+def test_get_commits_last_modified_lines_hyper_blame_unblamable(tmp_path, repo: Git):
     p = tmp_path / "ignore.txt"
     p.write_text("540c7f31c18664a38190fafb6721b5174ff4a166")
 
@@ -470,10 +470,10 @@ def test_get_commits_last_modified_lines_hyper_blame_unblamable(tmp_path, repo:
     assert len(buggy_commits) == 0
 
 
-@pytest.mark.skipif(Git().version_info < (2, 23),
+@pytest.mark.skipif(PyGit().version_info < (2, 23),
                     reason="requires git 2.23 or higher")
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_hyper_blame_ignore_hash(tmp_path, repo: GitRepository):
+def test_get_commits_last_modified_lines_hyper_blame_ignore_hash(tmp_path, repo: Git):
     p = tmp_path / "ignore.txt"
     p.write_text("5cb9e9ae44a0949ec91d06a955975289be766f34")
 
@@ -487,7 +487,7 @@ def test_get_commits_last_modified_lines_hyper_blame_ignore_hash(tmp_path, repo:
 
 
 @pytest.mark.parametrize('repo', ['test-repos/szz/'], indirect=True)
-def test_get_commits_last_modified_lines_hyper_blame_with_renaming(repo: GitRepository):
+def test_get_commits_last_modified_lines_hyper_blame_with_renaming(repo: Git):
     buggy_commits = repo.get_commits_last_modified_lines(repo.get_commit(
         'be0772cbaa2eba32bf97aae885199d1a357ddc93'))
 
diff --git a/tests/test_hashable.py b/tests/test_hashable.py
new file mode 100644
index 0000000..70c4ec5
--- /dev/null
+++ b/tests/test_hashable.py
@@ -0,0 +1,56 @@
+from pydriller.git import Git
+import pytest
+import logging
+
+logging.basicConfig(
+    format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO
+)
+
+
+@pytest.fixture
+def repo(request):
+    gr = Git(request.param)
+    yield gr
+    gr.clear()
+
+
+@pytest.mark.parametrize("repo", ["test-repos/complex_repo"], indirect=True)
+def test_commit_dictset(repo: Git):
+    c1 = repo.get_commit("e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2")
+    c2 = repo.get_commit(c1.parents[0])
+    c3 = repo.get_commit("a4ece0762e797d2e2dcbd471115108dd6e05ff58")
+
+    commit_dict = {c1: c1.hash, c2: c2.hash, c3: c3.hash}
+
+    assert type(commit_dict) == dict
+    assert commit_dict[c1] == "e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2"
+    assert commit_dict[c2] == c1.parents[0]
+    assert commit_dict[c3] == "a4ece0762e797d2e2dcbd471115108dd6e05ff58"
+    assert commit_dict[c1] != commit_dict[c2]
+
+    commit_set = {c1, c2, c3}
+    assert type(commit_set) == set
+    assert c1 in commit_set
+    assert commit_set - {c1} == {c2, c3}
+
+
+@pytest.mark.parametrize("repo", ["test-repos/complex_repo"], indirect=True)
+def test_modification_dictset(repo: Git):
+    c1 = repo.get_commit("e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2")
+    c2 = repo.get_commit(c1.parents[0])
+
+    m1 = c1.modified_files[0]
+    m2s = c2.modified_files
+
+    mod_dict = {m1: c1, m2s[0]: c2, m2s[1]: c2}
+
+    assert type(mod_dict) == dict
+    assert mod_dict[m1].hash == "e7d13b0511f8a176284ce4f92ed8c6e8d09c77f2"
+    assert mod_dict[m2s[0]].hash == c1.parents[0]
+    assert mod_dict[m2s[1]].hash == c1.parents[0]
+    assert m1 != m2s[0]
+
+    mod_set = {m1}.union(set(m2s))
+    assert type(mod_set) == set
+    assert m1 in mod_set
+    assert mod_set - {m1} == set(m2s)
diff --git a/tests/test_memory_consumption.py b/tests/test_memory_consumption.py
index fd58233..c8e5c13 100644
--- a/tests/test_memory_consumption.py
+++ b/tests/test_memory_consumption.py
@@ -17,7 +17,7 @@ import os
 import platform
 import sys
 import psutil
-from pydriller import RepositoryMining
+from pydriller import Repository
 from datetime import datetime
 
 logging.basicConfig(level=logging.WARNING)
@@ -114,9 +114,9 @@ def mine(_type):
     all_commits = []
 
     start = datetime.now()
-    for commit in RepositoryMining('test-repos-hadoop/hadoop',
-                                   since=dt1,
-                                   to=dt2).traverse_commits():
+    for commit in Repository('test-repos-hadoop/hadoop',
+                             since=dt1,
+                             to=dt2).traverse_commits():
         memory = p.memory_info()[0] / (2 ** 20)
         all_commits.append(memory)
 
@@ -125,7 +125,7 @@ def mine(_type):
         if _type == 0:
             continue
 
-        for mod in commit.modifications:
+        for mod in commit.modified_files:
             dd = mod.diff  # noqa
 
             if _type == 1:
diff --git a/tests/test_repository_mining.py b/tests/test_repository_mining.py
index 86f98fa..d153ccd 100644
--- a/tests/test_repository_mining.py
+++ b/tests/test_repository_mining.py
@@ -4,7 +4,7 @@ import os
 import pytest
 import sys
 
-from pydriller import RepositoryMining, GitRepository
+from pydriller import Repository, Git
 
 logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s',
                     level=logging.INFO)
@@ -12,18 +12,18 @@ logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s',
 
 @pytest.fixture
 def repo(request):
-    return list(RepositoryMining(path_to_repo=request.param).traverse_commits())
+    return list(Repository(path_to_repo=request.param).traverse_commits())
 
 
 @pytest.fixture
 def repo_to(request):
     path, to = request.param
-    return list(RepositoryMining(path_to_repo=path, to=to).traverse_commits())
+    return list(Repository(path_to_repo=path, to=to).traverse_commits())
 
 
 @pytest.fixture()
 def git_repo(request):
-    gr = GitRepository(request.param)
+    gr = Git(request.param)
     yield gr
     gr.clear()
 
@@ -31,13 +31,13 @@ def git_repo(request):
 # It should fail when no URLs are specified
 def test_no_url():
     with pytest.raises(Exception):
-        list(RepositoryMining().traverse_commits())
+        list(Repository().traverse_commits())
 
 
 # It should fail when URL is not a string or a List
 def test_badly_formatted_repo_url():
     with pytest.raises(Exception):
-        list(RepositoryMining(path_to_repo=set('repo')).traverse_commits())
+        list(Repository(path_to_repo=set('repo')).traverse_commits())
 
 
 @pytest.mark.parametrize('repo,expected', [
@@ -98,21 +98,21 @@ def test_both_local_and_remote_urls_list(repo_to, expected):
 
 def test_badly_formatted_url():
     with pytest.raises(Exception):
-        list(RepositoryMining(
+        list(Repository(
             path_to_repo='https://github.com/ishepard.git/test')
              .traverse_commits())
 
     with pytest.raises(Exception):
-        list(RepositoryMining(path_to_repo='test').traverse_commits())
+        list(Repository(path_to_repo='test').traverse_commits())
 
 
 @pytest.mark.parametrize('git_repo', ["test-repos/histogram"], indirect=True)
 def test_diff_without_histogram(git_repo):
     # without histogram
-    commit = list(RepositoryMining('test-repos/histogram',
-                                   single="93df8676e6fab70d9677e94fd0f6b17db095e890").traverse_commits())[0]
+    commit = list(Repository('test-repos/histogram',
+                             single="93df8676e6fab70d9677e94fd0f6b17db095e890").traverse_commits())[0]
 
-    diff = commit.modifications[0].diff_parsed
+    diff = commit.modified_files[0].diff_parsed
     assert len(diff['added']) == 11
     assert (3, '    if (path == null)') in diff['added']
     assert (5, '        log.error("Icon path is null");') in diff['added']
@@ -139,10 +139,10 @@ def test_diff_without_histogram(git_repo):
 @pytest.mark.parametrize('git_repo', ["test-repos/histogram"], indirect=True)
 def test_diff_with_histogram(git_repo):
     # with histogram
-    commit = list(RepositoryMining('test-repos/histogram',
-                                   single="93df8676e6fab70d9677e94fd0f6b17db095e890",
-                                   histogram_diff=True).traverse_commits())[0]
-    diff = commit.modifications[0].diff_parsed
+    commit = list(Repository('test-repos/histogram',
+                             single="93df8676e6fab70d9677e94fd0f6b17db095e890",
+                             histogram_diff=True).traverse_commits())[0]
+    diff = commit.modified_files[0].diff_parsed
     assert (4, '    {') in diff["added"]
     assert (5, '        log.error("Icon path is null");') in diff["added"]
     assert (6, '        return null;') in diff["added"]
@@ -163,26 +163,26 @@ def test_diff_with_histogram(git_repo):
 
 
 def test_ignore_add_whitespaces():
-    commit = list(RepositoryMining('test-repos/whitespace',
-                                   single="338a74ceae164784e216555d930210371279ba8e").traverse_commits())[0]
-    assert len(commit.modifications) == 1
-    commit = list(RepositoryMining('test-repos/whitespace',
-                                   skip_whitespaces=True,
-                                   single="338a74ceae164784e216555d930210371279ba8e").traverse_commits())[0]
-    assert len(commit.modifications) == 0
+    commit = list(Repository('test-repos/whitespace',
+                             single="338a74ceae164784e216555d930210371279ba8e").traverse_commits())[0]
+    assert len(commit.modified_files) == 1
+    commit = list(Repository('test-repos/whitespace',
+                             skip_whitespaces=True,
+                             single="338a74ceae164784e216555d930210371279ba8e").traverse_commits())[0]
+    assert len(commit.modified_files) == 0
 
 
 @pytest.mark.parametrize('git_repo', ["test-repos/whitespace"], indirect=True)
 def test_ignore_add_whitespaces_and_modified_normal_line(git_repo):
-    commit = list(RepositoryMining('test-repos/whitespace',
-                                   single="52716ef1f11e07308b5df1b313aec5496d5e91ce").traverse_commits())[0]
-    assert len(commit.modifications) == 1
-    parsed_normal_diff = commit.modifications[0].diff_parsed
-    commit = list(RepositoryMining('test-repos/whitespace',
-                                   skip_whitespaces=True,
-                                   single="52716ef1f11e07308b5df1b313aec5496d5e91ce").traverse_commits())[0]
-    assert len(commit.modifications) == 1
-    parsed_wo_whitespaces_diff = commit.modifications[0].diff_parsed
+    commit = list(Repository('test-repos/whitespace',
+                             single="52716ef1f11e07308b5df1b313aec5496d5e91ce").traverse_commits())[0]
+    assert len(commit.modified_files) == 1
+    parsed_normal_diff = commit.modified_files[0].diff_parsed
+    commit = list(Repository('test-repos/whitespace',
+                             skip_whitespaces=True,
+                             single="52716ef1f11e07308b5df1b313aec5496d5e91ce").traverse_commits())[0]
+    assert len(commit.modified_files) == 1
+    parsed_wo_whitespaces_diff = commit.modified_files[0].diff_parsed
     assert len(parsed_normal_diff['added']) == 2
     assert len(parsed_wo_whitespaces_diff['added']) == 1
 
@@ -191,29 +191,29 @@ def test_ignore_add_whitespaces_and_modified_normal_line(git_repo):
 
 
 def test_ignore_deleted_whitespaces():
-    commit = list(RepositoryMining('test-repos/whitespace',
-                                   single="e6e429f6b485e18fb856019d9953370fd5420b20").traverse_commits())[0]
-    assert len(commit.modifications) == 1
-    commit = list(RepositoryMining('test-repos/whitespace',
-                                   skip_whitespaces=True,
-                                   single="e6e429f6b485e18fb856019d9953370fd5420b20").traverse_commits())[0]
-    assert len(commit.modifications) == 0
+    commit = list(Repository('test-repos/whitespace',
+                             single="e6e429f6b485e18fb856019d9953370fd5420b20").traverse_commits())[0]
+    assert len(commit.modified_files) == 1
+    commit = list(Repository('test-repos/whitespace',
+                             skip_whitespaces=True,
+                             single="e6e429f6b485e18fb856019d9953370fd5420b20").traverse_commits())[0]
+    assert len(commit.modified_files) == 0
 
 
 def test_ignore_add_whitespaces_and_changed_file():
-    commit = list(RepositoryMining('test-repos/whitespace',
-                                   single="532068e9d64b8a86e07eea93de3a57bf9e5b4ae0").traverse_commits())[0]
-    assert len(commit.modifications) == 2
-    commit = list(RepositoryMining('test-repos/whitespace',
-                                   skip_whitespaces=True,
-                                   single="532068e9d64b8a86e07eea93de3a57bf9e5b4ae0").traverse_commits())[0]
-    assert len(commit.modifications) == 1
+    commit = list(Repository('test-repos/whitespace',
+                             single="532068e9d64b8a86e07eea93de3a57bf9e5b4ae0").traverse_commits())[0]
+    assert len(commit.modified_files) == 2
+    commit = list(Repository('test-repos/whitespace',
+                             skip_whitespaces=True,
+                             single="532068e9d64b8a86e07eea93de3a57bf9e5b4ae0").traverse_commits())[0]
+    assert len(commit.modified_files) == 1
 
 
 def test_clone_repo_to(tmp_path):
     dt2 = datetime(2018, 10, 20)
     url = "https://github.com/ishepard/pydriller.git"
-    assert len(list(RepositoryMining(
+    assert len(list(Repository(
         path_to_repo=url,
         to=dt2,
         clone_repo_to=str(tmp_path)).traverse_commits())) == 159
@@ -222,8 +222,8 @@ def test_clone_repo_to(tmp_path):
 
 def test_clone_repo_to_not_existing():
     with pytest.raises(Exception):
-        list(RepositoryMining("https://github.com/ishepard/pydriller",
-                              clone_repo_to="NOTEXISTINGDIR").traverse_commits())
+        list(Repository("https://github.com/ishepard/pydriller",
+                        clone_repo_to="NOTEXISTINGDIR").traverse_commits())
 
 
 def test_projectname_multiple_repos():
@@ -232,7 +232,7 @@ def test_projectname_multiple_repos():
         'test-repos/files_in_directories',
         'test-repos/files_in_directories'
     ]
-    for commit in RepositoryMining(path_to_repo=repos).traverse_commits():
+    for commit in Repository(path_to_repo=repos).traverse_commits():
         assert commit.project_name == 'files_in_directories'
 
 
@@ -241,7 +241,7 @@ def test_projectname_multiple_repos_remote():
         'https://github.com/ishepard/pydriller',
         'test-repos/pydriller'
     ]
-    for commit in RepositoryMining(path_to_repo=repos).traverse_commits():
+    for commit in Repository(path_to_repo=repos).traverse_commits():
         assert commit.project_name == 'pydriller'
 
 
@@ -252,7 +252,7 @@ def test_deletion_remotes():
         'https://github.com/ishepard/pydriller'
     ]
     paths = set()
-    for commit in RepositoryMining(path_to_repo=repos).traverse_commits():
+    for commit in Repository(path_to_repo=repos).traverse_commits():
         paths.add(commit.project_path)
 
     for path in paths: