Repository: ishepard/pydriller
Pull request #180 (2021-06-01 11:57:55)

Fixed exception on repeated run of clone_repo_to
Hej, in this PR I modified the code so that repeated executions of the following form will work:

```python
gh_repo = "https://github.com/ishepard/pydriller.git"
repo_path = "/tmp/pydriller"
for commit in Repository(path_to_repo=gh_repo, clone_repo_to=repo_path).traverse_commits():
    pass
for commit in Repository(path_to_repo=gh_repo, clone_repo_to=repo_path).traverse_commits():
    pass
```

In its current implementation, the above code works for the first instantiation of `Repository`, where the repository is correctly cloned into the given directory. However, the second instantiation of `Repository` panics with an exception similar to the following. In essence, it does not work since Pydriller tries to clone the repository again.

```
Traceback (most recent call last):
  File "/Home/scripts/collect_gh_repos.py", line 90, in <module>
    main(args.outpath)
  File "/Home/scripts/collect_gh_repos.py", line 40, in main
    commits_df = collect_commits(projs_to_check, outpath)
  File "/Home/scripts/collect_gh_repos.py", line 19, in collect_commits
    for commit in Repository(
  File "/Home-Wp9NZyEL-py3.9/lib/python3.9/site-packages/pydriller/repository.py", line 207, in traverse_commits
    with self._prep_repo(path_repo=path_repo) as git:
  File "/usr/local/Cellar/python@3.9/3.9.0_2/Frameworks/Python.framework/Versions/3.9/lib/python3.9/contextlib.py", line 117, in __enter__
    return next(self.gen)
  File "/Home-Wp9NZyEL-py3.9/lib/python3.9/site-packages/pydriller/repository.py", line 170, in _prep_repo
    local_path_repo = self._clone_remote_repo(self._clone_folder(), path_repo)
  File "/Home-Wp9NZyEL-py3.9/lib/python3.9/site-packages/pydriller/repository.py", line 151, in _clone_remote_repo
    Repo.clone_from(url=repo, to_path=repo_folder)
  File "/Home-Wp9NZyEL-py3.9/lib/python3.9/site-packages/git/repo/base.py", line 1083, in clone_from
    return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
  File "/Home-Wp9NZyEL-py3.9/lib/python3.9/site-packages/git/repo/base.py", line 1021, in _clone
    finalize_process(proc, stderr=stderr)
  File "/Home-Wp9NZyEL-py3.9/lib/python3.9/site-packages/git/util.py", line 369, in finalize_process
    proc.wait(**kwargs)
  File "/Home-Wp9NZyEL-py3.9/lib/python3.9/site-packages/git/cmd.py", line 450, in wait
    raise GitCommandError(remove_password_if_present(self.args), status, errstr)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
  cmdline: git clone -v https://github.com/apache/ant.git /tmp/pydriller
  stderr: 'fatal: destination path '/tmp/pydriller' already exists and is not an empty directory.
```

Since I did not expect such a behaviour and since I think it is practical when running repeated analysis on a repository, I implemented this fix with test.
To me, it is especially useful during exploratory analyzes of large repositories for which I do not want to repeatedly write code that checks if I already cloned a repo with another script.


Patch:
diff --git a/pydriller/repository.py b/pydriller/repository.py
index 1adf822..2a2314c 100644
--- a/pydriller/repository.py
+++ b/pydriller/repository.py
@@ -147,8 +147,11 @@ class Repository:
 
     def _clone_remote_repo(self, tmp_folder: str, repo: str) -> str:
         repo_folder = os.path.join(tmp_folder, self._get_repo_name_from_url(repo))
-        logger.info("Cloning %s in temporary folder %s", repo, repo_folder)
-        Repo.clone_from(url=repo, to_path=repo_folder)
+        if os.path.isdir(repo_folder):
+            logger.info("Reusing folder %s for %s", repo_folder, repo)
+        else:
+            logger.info("Cloning %s in temporary folder %s", repo, repo_folder)
+            Repo.clone_from(url=repo, to_path=repo_folder)
 
         return repo_folder
 


Test patch:
diff --git a/tests/test_repository_mining.py b/tests/test_repository_mining.py
index d153ccd..5e347e6 100644
--- a/tests/test_repository_mining.py
+++ b/tests/test_repository_mining.py
@@ -226,6 +226,23 @@ def test_clone_repo_to_not_existing():
                         clone_repo_to="NOTEXISTINGDIR").traverse_commits())
 
 
+def test_clone_repo_to_repeated():
+    import tempfile
+    tmp_path = tempfile.gettempdir()
+    dt2 = datetime(2018, 10, 20)
+    url = "https://github.com/ishepard/pydriller.git"
+    assert len(list(Repository(
+        path_to_repo=url,
+        to=dt2,
+        clone_repo_to=str(tmp_path)).traverse_commits())) == 159
+    assert os.path.isdir(os.path.join(tmp_path, "pydriller"))
+    assert len(list(Repository(
+        path_to_repo=url,
+        to=dt2,
+        clone_repo_to=str(tmp_path)).traverse_commits())) == 159
+    assert os.path.isdir(os.path.join(tmp_path, "pydriller"))
+
+
 def test_projectname_multiple_repos():
     repos = [
         'test-repos/files_in_directories',