Repository: ishepard/pydriller (python)

Issue:
Method traverse_commits() returns more commits than expected for some repositories
For some repositories like ```UnderGreen/ansible-role-mongodb``` I get wrong results when calling the method ```traverse_commits()```.

**How to reproduce:**

```
from pydriller.repository_mining import RepositoryMining

for commit in RepositoryMining('https://github.com/UnderGreen/ansible-role-mongodb',
                                from_commit='3efddcf91d51dd55a7d322ec9e0f14f673da3d1a',
                                to_commit='416fb2daf88410bd928d310e75e2ff4447be30e2').traverse_commits():
    print(commit.hash)
```

**Expected result:**

```
3efddcf91d51dd55a7d322ec9e0f14f673da3d1a
abf8ded23ecc712e1ce4347403ded96f374e761b
527214bcc9311a0e42868144f97982878e33c9f3
454df2ef69d0bc3c13dc0eee748595885c3a0036
416fb2daf88410bd928d310e75e2ff4447be30e2
```

**Actual result:**

```
2dba294ff88b3c7fbf28802c5179c79292ab353e
5f167f612a9bd52bc8983ec6f3f143e7e5daca82
9e17ec9ed8ab36defed2017f9495e71a2688dfab
d6aa8e0ffa70ec3590a01eaec65626209db6c1b1
3efddcf91d51dd55a7d322ec9e0f14f673da3d1a     [correct]
abf8ded23ecc712e1ce4347403ded96f374e761b     [correct]
527214bcc9311a0e42868144f97982878e33c9f3     [correct]
454df2ef69d0bc3c13dc0eee748595885c3a0036     [correct]
416fb2daf88410bd928d310e75e2ff4447be30e2     [correct]
d8d847825d97f842355dbaf6a361089b141bdc41
5c04b2c8bb6fc31994974e84adab155c80d448eb
30c2bc37c4fbc4b95bbee9e01566400e070f4ab0
4c3919d9201e38d47de52a4882652d7ba01312a5
845f25d5a2dec1c02b6798f7e95d77ea3768c307
```

**Version:**
I've installed the latest version locally (from commit ```e4449b602be0dff8dc0a49a466ec3dbf29ec88fb```).

The same behavior appears in PyDriller-1.10.1.


Pull request:
Fix from_commit and to_commit filters
Fix #81 

Patch:
diff --git a/docs/configuration.rst b/docs/configuration.rst
index 3cef23b..40319cc 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -4,7 +4,7 @@
 Configuration
 =============
 
-One of the main advantage of using PyDriller to mine software repositories, is that is highly configurable. Let's start with selecting which commit to analyze.
+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.
 
 Selecting projects to analyze
 =============================
@@ -49,7 +49,7 @@ By default, PyDriller analyzes all the commits in the repository. However, filte
 
 *ORDER*:
 
-* **reversed\_order** *(bool)*: by default PyDriller returns the commits in chronological order (from the oldest to the newest, the contrary of `git log`). If you need viceversa instead, put this field to **True**.
+* **reversed\_order** *(bool)*: by default PyDriller returns the commits in chronological order (from the oldest to the newest, the contrary of `git log`). If you need viceversa instead, put this field to **True**. **NOTE**: if you use reverse_order together from\_commit and to\_commit, you need to set them according to the order (e.g., if the order of commits is A -> B -> C, ).
 
 Examples::
 
diff --git a/pydriller/metrics/process/commits_count.py b/pydriller/metrics/process/commits_count.py
index 38823b3..83821a3 100644
--- a/pydriller/metrics/process/commits_count.py
+++ b/pydriller/metrics/process/commits_count.py
@@ -21,11 +21,9 @@ class CommitsCount(ProcessMetric):
                                        from_commit=self.from_commit,
                                        to_commit=self.to_commit,
                                        reversed_order=True).traverse_commits():
-
             for modified_file in commit.modifications:
 
                 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
 
diff --git a/pydriller/utils/conf.py b/pydriller/utils/conf.py
index f33ebb8..3121212 100644
--- a/pydriller/utils/conf.py
+++ b/pydriller/utils/conf.py
@@ -67,6 +67,20 @@ class Conf:
         Check if the values passed by the user are correct.
 
         """
+        self.check_starting_commit()
+        self.check_ending_commit()
+        self._check_timezones()
+
+        if self.get("from_commit") and self.get("to_commit") and self.get(
+                "from_commit") == self.get("to_commit"):
+            logger.warning("You should not point from_commit and "
+                           "to_commit to the same commit, but use the "
+                           "'single' filter instead.")
+            single = self.get("to_commit")
+            self.set_value("from_commit", None)
+            self.set_value("to_commit", None)
+            self.set_value("single", single)
+
         if self.get('single') is not None:
             if any([self.get('since'),
                     self.get('to'),
@@ -77,27 +91,6 @@ class Conf:
                 raise Exception('You can not specify a single commit with '
                                 'other filters')
 
-        self.check_starting_commit()
-        self.check_ending_commit()
-        self._check_timezones()
-
-    def check_ending_commit(self):
-        """
-        Get the ending commit from the 'to', 'to_commit' or 'to_tag' filter.
-        """
-        if not self.only_one_filter([self.get('to'),
-                                     self.get('to_commit'),
-                                     self.get('to_tag')]):
-            raise Exception('You can only specify one between since, '
-                            'from_tag and from_commit')
-        git_repo = self.get("git_repo")
-        if self.get('to_commit') is not None:
-            self.set_value('to', git_repo.get_commit(self.get(
-                'to_commit')).committer_date)
-        if self.get('to_tag') is not None:
-            self.set_value('to', git_repo.get_commit_from_tag(
-                self.get('to_tag')).committer_date)
-
     def check_starting_commit(self):
         """
         Get the starting commit from the 'since', 'from_commit' or 'from_tag'
@@ -108,13 +101,22 @@ class Conf:
                                      self.get('from_tag')]):
             raise Exception('You can only specify one between since, '
                             'from_tag and from_commit')
-        git_repo = self.get("git_repo")
-        if self.get('from_commit') is not None:
-            self.set_value('since', git_repo.get_commit(
-                self.get('from_commit')).committer_date)
         if self.get('from_tag') is not None:
-            self.set_value('since', git_repo.get_commit_from_tag(
-                self.get('from_tag')).committer_date)
+            self.set_value('from_commit', self.get(
+                "git_repo").get_commit_from_tag(self.get('from_tag')).hash)
+
+    def check_ending_commit(self):
+        """
+        Get the ending commit from the 'to', 'to_commit' or 'to_tag' filter.
+        """
+        if not self.only_one_filter([self.get('to'),
+                                     self.get('to_commit'),
+                                     self.get('to_tag')]):
+            raise Exception('You can only specify one between since, '
+                            'from_tag and from_commit')
+        if self.get('to_tag') is not None:
+            self.set_value('to_commit', self.get(
+                "git_repo").get_commit_from_tag(self.get('to_tag')).hash)
 
     @staticmethod
     def only_one_filter(arr):
@@ -136,14 +138,30 @@ class Conf:
         """
         if self.get('single') is not None and \
                 commit.hash != self.get('single'):
-            logger.debug(
-                'Commit filtered because is not the defined in single')
+            logger.debug('Commit filtered because is not '
+                         'the defined in single')
             return True
         if (self.get('since') is not None and
                 commit.committer_date < self.get('since')) or \
                 (self.get('to') is not None and
                  commit.committer_date > self.get('to')):
             return True
+        if self.get('from_commit') is not None and \
+                self.get('from_commit') == commit.hash:
+            self.set_value('from_commit_started', True)
+            return False
+        if self.get('from_commit') is not None and \
+                self.get('from_commit') != commit.hash and \
+                self.get('from_commit_started') is None:
+            return True
+        if self.get('to_commit') is not None and \
+                self.get('to_commit') != commit.hash and \
+                self.get('to_commit_reached') is not None:
+            return True
+        if self.get('to_commit') is not None and \
+                self.get('to_commit') == commit.hash:
+            self.set_value('to_commit_reached', True)
+            return False
         if self.get('only_modifications_with_file_types') is not None:
             if not self._has_modification_with_file_type(commit):
                 logger.debug('Commit filtered for modification types')
@@ -169,7 +187,6 @@ class Conf:
                 commit.hash not in self.get('tagged_commits'):
             logger.debug("Commit filtered because it is not tagged")
             return True
-
         return False
 
     def _has_modification_with_file_type(self, commit):


Test patch:
diff --git a/tests/integration/test_ranges.py b/tests/integration/test_ranges.py
index 3b3efc2..e3ab613 100644
--- a/tests/integration/test_ranges.py
+++ b/tests/integration/test_ranges.py
@@ -77,6 +77,12 @@ def repository_mining_cc(path, from_commit, to_commit):
 def repository_mining_tt(path, from_tag, to_tag):
     return list(RepositoryMining(path, from_tag=from_tag, to_tag=to_tag).traverse_commits())
 
+@pytest.fixture
+def repository_mining_complex_tags(path, from_tag, to_tag):
+    return list(RepositoryMining('test-repos/tags',
+                                 from_tag=from_tag,
+                                 to_tag=to_tag).traverse_commits())
+
 
 @pytest.mark.parametrize('to,expected_commits', [
     (None, 5),
@@ -139,10 +145,12 @@ def test_from_tag_filter(repository_mining_tt, expected_commits):
 
 
 @pytest.mark.parametrize('from_tag,to_tag,expected_commits', [
-    ('v1.4', 'v1.4', 1),
+    ('tag1', 'tag2', 3),
+    ('tag1', 'tag3', 5),
+    ('tag2', 'tag3', 3),
 ])
-def test_from_and_to_tag(repository_mining_tt, expected_commits):
-    assert len(repository_mining_tt) == expected_commits
+def test_from_and_to_tag(repository_mining_complex_tags, expected_commits):
+    assert len(repository_mining_complex_tags) == expected_commits
 
 
 def test_multiple_filters_exceptions():
diff --git a/tests/integration/test_reverse_order.py b/tests/integration/test_reverse_order.py
index c9dec84..8069d98 100644
--- a/tests/integration/test_reverse_order.py
+++ b/tests/integration/test_reverse_order.py
@@ -12,33 +12,40 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from pydriller.repository_mining import RepositoryMining
 import logging
 
 logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
 
-import pytest
-
-from pydriller.repository_mining import RepositoryMining
-
-
-@pytest.yield_fixture(scope="function")
-def lc(request):
-    reversed = request.param
-    yield list(RepositoryMining('test-repos/different_files',
-                                reversed_order=reversed).traverse_commits())
-
-
-@pytest.mark.parametrize('lc', [False], indirect=True)
-def test_should_visit_ascendent_order(lc):
-    assert len(lc) == 3
-    assert lc[0].hash == 'a1b6136f978644ff1d89816bc0f2bd86f6d9d7f5'
-    assert lc[1].hash == '375de7a8275ecdc0b28dc8de2568f47241f443e9'
-    assert lc[2].hash == 'b8c2be250786975f1c6f47e96922096f1bb25e39'
-
 
-@pytest.mark.parametrize('lc', [True], indirect=True)
-def test_should_visit_descendent_order(lc):
+def test_should_visit_ascendent_order():
+    lc = list(RepositoryMining('test-repos/small_repo',
+                               reversed_order=False).traverse_commits())
+    assert len(lc) == 5
+    assert lc[0].hash == 'a88c84ddf42066611e76e6cb690144e5357d132c'
+    assert lc[1].hash == '6411e3096dd2070438a17b225f44475136e54e3a'
+    assert lc[2].hash == '09f6182cef737db02a085e1d018963c7a29bde5a'
+    assert lc[3].hash == '1f99848edadfffa903b8ba1286a935f1b92b2845'
+    assert lc[4].hash == 'da39b1326dbc2edfe518b90672734a08f3c13458'
+
+
+def test_should_visit_descendent_order():
+    lc = list(RepositoryMining('test-repos/small_repo',
+                               reversed_order=True).traverse_commits())
+    assert len(lc) == 5
+    assert lc[0].hash == 'da39b1326dbc2edfe518b90672734a08f3c13458'
+    assert lc[1].hash == '1f99848edadfffa903b8ba1286a935f1b92b2845'
+    assert lc[2].hash == '09f6182cef737db02a085e1d018963c7a29bde5a'
+    assert lc[3].hash == '6411e3096dd2070438a17b225f44475136e54e3a'
+    assert lc[4].hash == 'a88c84ddf42066611e76e6cb690144e5357d132c'
+
+
+def test_should_visit_descendent_order_with_filters():
+    lc = list(RepositoryMining('test-repos/small_repo',
+                               from_commit='1f99848edadfffa903b8ba1286a935f1b92b2845',
+                               to_commit='6411e3096dd2070438a17b225f44475136e54e3a',
+                               reversed_order=True).traverse_commits())
     assert len(lc) == 3
-    assert lc[2].hash == 'a1b6136f978644ff1d89816bc0f2bd86f6d9d7f5'
-    assert lc[1].hash == '375de7a8275ecdc0b28dc8de2568f47241f443e9'
-    assert lc[0].hash == 'b8c2be250786975f1c6f47e96922096f1bb25e39'
+    assert lc[0].hash == '1f99848edadfffa903b8ba1286a935f1b92b2845'
+    assert lc[1].hash == '09f6182cef737db02a085e1d018963c7a29bde5a'
+    assert lc[2].hash == '6411e3096dd2070438a17b225f44475136e54e3a'
diff --git a/tests/metrics/process/test_commits_count.py b/tests/metrics/process/test_commits_count.py
index c1ab94b..1a54796 100644
--- a/tests/metrics/process/test_commits_count.py
+++ b/tests/metrics/process/test_commits_count.py
@@ -5,17 +5,18 @@ import pytest
 from pydriller.metrics.process.commits_count import CommitsCount
 
 TEST_DATA = [
-    ('test-repos/pydriller', 'domain/developer.py', 'ab36bf45859a210b0eae14e17683f31d19eea041', 'fdf671856b260aca058e6595a96a7a0fba05454b', 2),
-    ('test-repos/pydriller', 'domain/developer.py', None, 'fdf671856b260aca058e6595a96a7a0fba05454b', 2),
+    ('test-repos/pydriller', 'domain/developer.py', 'fdf671856b260aca058e6595a96a7a0fba05454b', 'ab36bf45859a210b0eae14e17683f31d19eea041', 2),
+    ('test-repos/pydriller', 'domain/developer.py', 'fdf671856b260aca058e6595a96a7a0fba05454b', None, 2),
     ('test-repos/pydriller', 'pydriller/domain/developer.py', None, None, 8),
 ]
 
-@pytest.mark.parametrize('path_to_repo, filepath, from_commit, to_commit, expected', TEST_DATA)
+
+@pytest.mark.parametrize('path_to_repo, filepath, from_commit, '
+                         'to_commit, expected', TEST_DATA)
 def test(path_to_repo, filepath, from_commit, to_commit, expected):
     metric = CommitsCount(path_to_repo=path_to_repo,
                           from_commit=from_commit,
                           to_commit=to_commit)
-
     count = metric.count()
     filepath = str(Path(filepath))
     assert count[filepath] == expected
diff --git a/tests/metrics/process/test_contributors_count.py b/tests/metrics/process/test_contributors_count.py
index 6bbbfdf..b15e6a2 100644
--- a/tests/metrics/process/test_contributors_count.py
+++ b/tests/metrics/process/test_contributors_count.py
@@ -5,8 +5,8 @@ import pytest
 from pydriller.metrics.process.contributors_count import ContributorsCount
 
 TEST_DATA = [
-   ('test-repos/pydriller', 'pydriller/git_repository.py', '115953109b57d841ccd0952d70f8ed6703d175cd', '8b69cae085581256adfdbd58c0e499395819b84d', 2),
-   ('test-repos/pydriller', 'domain/modification.py', None, '71e053f61fc5d31b3e31eccd9c79df27c31279bf', 1),
+   ('test-repos/pydriller', 'pydriller/git_repository.py', '8b69cae085581256adfdbd58c0e499395819b84d', '115953109b57d841ccd0952d70f8ed6703d175cd', 2),
+   ('test-repos/pydriller', 'domain/modification.py', '71e053f61fc5d31b3e31eccd9c79df27c31279bf', None, 1),
    ('test-repos/pydriller', 'pydriller/domain/developer.py', None, None, 1)
 ]
 
diff --git a/tests/metrics/process/test_contributors_experience_count.py b/tests/metrics/process/test_contributors_experience_count.py
index 8f4c7a9..3055801 100644
--- a/tests/metrics/process/test_contributors_experience_count.py
+++ b/tests/metrics/process/test_contributors_experience_count.py
@@ -6,9 +6,9 @@ from pydriller.metrics.process.contributors_experience import \
     ContributorsExperience
 
 TEST_DATA = [
-   ('test-repos/pydriller', 'domain/modification.py', None, 'fdf671856b260aca058e6595a96a7a0fba05454b', 100.0),
+   ('test-repos/pydriller', 'domain/modification.py', 'fdf671856b260aca058e6595a96a7a0fba05454b', None, 100.0),
    ('test-repos/pydriller', 'pydriller/git_repository.py', 'e9854bbea1cb7b7f06cbb559f7b06724d11ae1e5', 'e9854bbea1cb7b7f06cbb559f7b06724d11ae1e5', 100.0),
-   ('test-repos/pydriller', 'pydriller/git_repository.py', '9d0924301e4fae00eea6d00945bf834455e9a2a6', 'e9854bbea1cb7b7f06cbb559f7b06724d11ae1e5', round(100*28/30, 2))
+   ('test-repos/pydriller', 'pydriller/git_repository.py', 'e9854bbea1cb7b7f06cbb559f7b06724d11ae1e5', '9d0924301e4fae00eea6d00945bf834455e9a2a6', round(100*28/30, 2))
 ]
 
 @pytest.mark.parametrize('path_to_repo, filepath, from_commit, to_commit, expected', TEST_DATA)
diff --git a/tests/metrics/process/test_history_complexity_count.py b/tests/metrics/process/test_history_complexity_count.py
index 0934d73..e63d4c6 100644
--- a/tests/metrics/process/test_history_complexity_count.py
+++ b/tests/metrics/process/test_history_complexity_count.py
@@ -6,7 +6,7 @@ from pydriller.metrics.process.history_complexity import HistoryComplexity
 
 TEST_DATA = [
     ('test-repos/pydriller', 'scm/git_repository.py', '90ca34ebfe69629cb7f186a1582fc38a73cc572e', '90ca34ebfe69629cb7f186a1582fc38a73cc572e', 40.49),
-    ('test-repos/pydriller', 'scm/git_repository.py', '90ca34ebfe69629cb7f186a1582fc38a73cc572e', '71e053f61fc5d31b3e31eccd9c79df27c31279bf', 47.05)
+    ('test-repos/pydriller', 'scm/git_repository.py', '71e053f61fc5d31b3e31eccd9c79df27c31279bf', '90ca34ebfe69629cb7f186a1582fc38a73cc572e', 47.05)
 ]
 
 @pytest.mark.parametrize('path_to_repo, filepath, from_commit, to_commit, expected', TEST_DATA)
diff --git a/tests/metrics/process/test_lines_count.py b/tests/metrics/process/test_lines_count.py
index 11de691..d74f0a6 100644
--- a/tests/metrics/process/test_lines_count.py
+++ b/tests/metrics/process/test_lines_count.py
@@ -5,16 +5,16 @@ import pytest
 from pydriller.metrics.process.lines_count import LinesCount
 
 TEST_DATA = [
-   ('test-repos/pydriller', '.gitignore', None, 'ab36bf45859a210b0eae14e17683f31d19eea041',
+   ('test-repos/pydriller', '.gitignore', 'ab36bf45859a210b0eae14e17683f31d19eea041', None,
       {'added': 197, 'removed': 0}),
 
    ('test-repos/pydriller', '.gitignore', 'ab36bf45859a210b0eae14e17683f31d19eea041', 'ab36bf45859a210b0eae14e17683f31d19eea041',
       {'added': 197, 'removed': 0}),
 
-   ('test-repos/pydriller', 'domain/modification.py', None, 'fdf671856b260aca058e6595a96a7a0fba05454b',
+   ('test-repos/pydriller', 'domain/modification.py', 'fdf671856b260aca058e6595a96a7a0fba05454b', None,
       {'added': 49, 'removed': 1}),
 
-   ('test-repos/pydriller', 'domain/modification.py', 'ab36bf45859a210b0eae14e17683f31d19eea041', 'fdf671856b260aca058e6595a96a7a0fba05454b',
+   ('test-repos/pydriller', 'domain/modification.py', 'fdf671856b260aca058e6595a96a7a0fba05454b', 'ab36bf45859a210b0eae14e17683f31d19eea041',
       {'added': 49, 'removed': 1})
 ]
 
diff --git a/tests/metrics/process/test_minor_contributors_count.py b/tests/metrics/process/test_minor_contributors_count.py
index bcf605b..5fa25cd 100644
--- a/tests/metrics/process/test_minor_contributors_count.py
+++ b/tests/metrics/process/test_minor_contributors_count.py
@@ -6,8 +6,8 @@ from pydriller.metrics.process.contributors_count import ContributorsCount
 
 TEST_DATA = [
    ('test-repos/pydriller', 'pydriller/git_repository.py', 'e9854bbea1cb7b7f06cbb559f7b06724d11ae1e5', 'e9854bbea1cb7b7f06cbb559f7b06724d11ae1e5', 0),
-   ('test-repos/pydriller', 'pydriller/git_repository.py', None, 'e9854bbea1cb7b7f06cbb559f7b06724d11ae1e5', 1),
-   ('test-repos/pydriller', 'pydriller/git_repository.py', None, '4af3839eb5ea5969f42142529a7a5526739fa570', 2)
+   ('test-repos/pydriller', 'pydriller/git_repository.py', 'e9854bbea1cb7b7f06cbb559f7b06724d11ae1e5', None, 1),
+   ('test-repos/pydriller', 'pydriller/git_repository.py', '4af3839eb5ea5969f42142529a7a5526739fa570', None, 2)
 ]
 
 @pytest.mark.parametrize('path_to_repo, filepath, from_commit, to_commit, expected', TEST_DATA)