nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: ishepard/pydriller
Pull request #51 (2019-07-19 09:56:41)

Automatically turn hashes_to_ignore path to absolute
The path passed to git hyper-blame must be absolute, it's better if we turn relative paths to absolute automatically instead of silently failing.


Patch:
diff --git a/pydriller/git_repository.py b/pydriller/git_repository.py
index 722e749..0e1e138 100644
--- a/pydriller/git_repository.py
+++ b/pydriller/git_repository.py
@@ -299,6 +299,8 @@ class GitRepository:
                                                "available. Install it by " \
                                                "cloning depot_tools and " \
                                                "adding it to your PATH."
+            hashes_to_ignore_path = os.path.realpath(hashes_to_ignore_path)
+            assert os.path.exists(hashes_to_ignore_path), "The file with the commit hashes to ignore does not exist"
 
         if modification is not None:
             modifications = [modification]


Test patch:
diff --git a/tests/test_git_repository.py b/tests/test_git_repository.py
index 920e5ee..6095396 100644
--- a/tests/test_git_repository.py
+++ b/tests/test_git_repository.py
@@ -490,7 +490,29 @@ def test_get_commits_last_modified_lines_hyper_blame_ignore_hash(depot_tools, tm
 
     gr = GitRepository('test-repos/test5/')
 
-    buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit('e6d3b38a9ef683e8184eac10a0471075c2808bbd'), hashes_to_ignore_path=tmpdir / "ignore")
+    buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit('e6d3b38a9ef683e8184eac10a0471075c2808bbd'), hashes_to_ignore_path=os.path.join(str(tmpdir), "ignore"))
+
+    assert len(buggy_commits) == 1
+    assert '22505e97dca6f843549b3a484b3609be4e3acf17' in buggy_commits[
+        'B.java']
+
+
+@pytest.fixture
+def tmpcwd(tmpdir):
+    cwd = os.getcwd()
+    os.chdir(str(tmpdir))
+    yield cwd
+    os.chdir(cwd)
+
+
+@pytest.mark.skipif(platform.system() == "Windows", reason="depot_tools is not easy to install on Windows CI")
+def test_get_commits_last_modified_lines_hyper_blame_ignore_hash_relative(depot_tools, tmpcwd):
+    with open("ignore", "w") as f:
+        f.write("540c7f31c18664a38190fafb6721b5174ff4a166")
+
+    gr = GitRepository(os.path.join(tmpcwd, 'test-repos/test5/'))
+
+    buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit('e6d3b38a9ef683e8184eac10a0471075c2808bbd'), hashes_to_ignore_path="ignore")
 
     assert len(buggy_commits) == 1
     assert '22505e97dca6f843549b3a484b3609be4e3acf17' in buggy_commits[
@@ -499,7 +521,7 @@ def test_get_commits_last_modified_lines_hyper_blame_ignore_hash(depot_tools, tm
 
 @pytest.mark.skipif(platform.system() == "Windows", reason="depot_tools is not easy to install on Windows CI")
 def test_get_commits_last_modified_lines_hyper_blame_with_renaming(
-        depot_tools, tmpdir):
+        depot_tools):
     gr = GitRepository('test-repos/test5/')
 
     buggy_commits = gr.get_commits_last_modified_lines(gr.get_commit('be0772cbaa2eba32bf97aae885199d1a357ddc93'))
Repository: ishepard/pydriller
Pull request #86 (2020-02-11 13:06:55)

Solved division by zero bug in history complexity metric
When only one file was modified in the evolution period ```log(p+1/1e10, n_files)``` returned div-by-zero error.


Patch:
diff --git a/pydriller/metrics/process/history_complexity.py b/pydriller/metrics/process/history_complexity.py
index 85cde4c..2a0cdfc 100644
--- a/pydriller/metrics/process/history_complexity.py
+++ b/pydriller/metrics/process/history_complexity.py
@@ -70,7 +70,9 @@ class HistoryComplexity(ProcessMetric):
             files[filepath] /= total_modifications
 
         # Normalized entropy
-        entropy = -sum(p*log(p+1/1e10, n_files) for p in files.values())
+        entropy = 0
+        if len(files.values()) > 1:
+            entropy = -sum(p*log(p+1/1e10, n_files) for p in files.values())
 
         for filepath in files:
             files[filepath] *= entropy


Test patch:
diff --git a/tests/metrics/process/test_history_complexity_count.py b/tests/metrics/process/test_history_complexity_count.py
index e63d4c6..bd57998 100644
--- a/tests/metrics/process/test_history_complexity_count.py
+++ b/tests/metrics/process/test_history_complexity_count.py
@@ -6,7 +6,8 @@ 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', '71e053f61fc5d31b3e31eccd9c79df27c31279bf', '90ca34ebfe69629cb7f186a1582fc38a73cc572e', 47.05)
+    ('test-repos/pydriller', 'scm/git_repository.py', '71e053f61fc5d31b3e31eccd9c79df27c31279bf', '90ca34ebfe69629cb7f186a1582fc38a73cc572e', 47.05),
+    ('https://github.com/geerlingguy/ansible-role-solr', 'tasks/main.yml', '7fb350c30be1124b51aab4a88352428e0a853b9a', '678429591513fe86045e892a1da680c8ac36e00f', .0)
 ]
 
 @pytest.mark.parametrize('path_to_repo, filepath, from_commit, to_commit, expected', TEST_DATA)
Repository: ishepard/pydriller
Pull request #99 (2020-03-06 11:19:03)

Added process metric to measure the number of files committed together
Metrics from paper [A Comparative Analysis of the Efficiency of Change Metrics and Static Code Attributes for Defect Prediction](https://dl.acm.org/doi/pdf/10.1145/1368088.1368114).

## ChangeSet
Added:

* ```max()``` - the maximum number of files committed together in the period ```[from_commit, to_commit]```

* ```avg()``` - the average number of files committed together in the period ```[from_commit, to_commit]```



Patch:
diff --git a/pydriller/metrics/process/change_set.py b/pydriller/metrics/process/change_set.py
new file mode 100644
index 0000000..cd7585c
--- /dev/null
+++ b/pydriller/metrics/process/change_set.py
@@ -0,0 +1,45 @@
+"""
+Module that calculates the number of files committed together.
+"""
+import statistics
+
+from pydriller.metrics.process.process_metric import ProcessMetric
+
+class ChangeSet(ProcessMetric):
+    """
+    This class is responsible to implement the Change Set metric that
+    measures the
+
+    * maximum number of files committed together - max();
+    * average number of files committed together - avg().
+    """
+
+    def __init__(self, path_to_repo: str,
+                 from_commit: str,
+                 to_commit: str):
+        super().__init__(path_to_repo, from_commit, to_commit)
+        self.__initialize()
+
+    def __initialize(self):
+
+        self.committed_together = []
+
+        for commit in self.repo_miner.traverse_commits():
+            self.committed_together.append(len(commit.modifications))
+
+    def max(self):
+        """
+        Return the maximum number of files committed together.
+
+        :return: int max number of files committed together
+        """
+        return max(self.committed_together)
+
+    def avg(self):
+        """
+        Return the average number of files committed together.
+
+        :return: int avg number of files rounded off to the nearest integer
+        """
+
+        return round(statistics.mean(self.committed_together))


Test patch:
diff --git a/tests/metrics/process/test_change_set.py b/tests/metrics/process/test_change_set.py
new file mode 100644
index 0000000..dbe51e8
--- /dev/null
+++ b/tests/metrics/process/test_change_set.py
@@ -0,0 +1,18 @@
+import pytest
+from pydriller.metrics.process.change_set import ChangeSet
+
+TEST_DATA = [
+    ('test-repos/pydriller', 'ab36bf45859a210b0eae14e17683f31d19eea041', '71e053f61fc5d31b3e31eccd9c79df27c31279bf', 13, 8)
+]
+
+@pytest.mark.parametrize('path_to_repo, from_commit, to_commit, expected_max, expected_avg', TEST_DATA)
+def test(path_to_repo, from_commit, to_commit, expected_max, expected_avg):
+    metric = ChangeSet(path_to_repo=path_to_repo,
+                       from_commit=from_commit,
+                       to_commit=to_commit)
+
+    actual_max = metric.max()
+    actual_avg = metric.avg()
+
+    assert actual_max == expected_max
+    assert actual_avg == expected_avg
Repository: ishepard/pydriller
Pull request #108 (2020-03-20 10:53:06)

Fixed ValueError: max() arg is an empty sequence and statistics.Stati…
…sticsError: mean requires at least one data point in change_set.py


Patch:
diff --git a/pydriller/metrics/process/change_set.py b/pydriller/metrics/process/change_set.py
index b502bf2..cdc9e0d 100644
--- a/pydriller/metrics/process/change_set.py
+++ b/pydriller/metrics/process/change_set.py
@@ -36,7 +36,7 @@ class ChangeSet(ProcessMetric):
 
         :return: int max number of files committed together
         """
-        return max(self.committed_together)
+        return max(self.committed_together, default=0)
 
     def avg(self):
         """
@@ -44,5 +44,7 @@ class ChangeSet(ProcessMetric):
 
         :return: int avg number of files rounded off to the nearest integer
         """
+        if not self.committed_together:
+            return 0
 
         return round(statistics.mean(self.committed_together))


Test patch:
diff --git a/tests/metrics/process/test_change_set.py b/tests/metrics/process/test_change_set.py
index 9ea893a..1d44735 100644
--- a/tests/metrics/process/test_change_set.py
+++ b/tests/metrics/process/test_change_set.py
@@ -20,7 +20,8 @@ def test_with_commits(path_to_repo, from_commit, to_commit, expected_max, expect
 
 
 TEST_DATA = [
-    ('test-repos/pydriller', datetime(2018, 3, 21), datetime(2018, 3, 27), 13, 8)
+    ('test-repos/pydriller', datetime(2018, 3, 21), datetime(2018, 3, 27), 13, 8),
+    ('test-repos/pydriller', datetime(2018, 3, 23), datetime(2018, 3, 23), 0, 0)
 ]
 
 @pytest.mark.parametrize('path_to_repo, since, to, expected_max, expected_avg', TEST_DATA)
Repository: ishepard/pydriller
Pull request #151 (2020-12-31 17:13:16)

Added insertions/deletions/lines/files properities for easy commit info collection in commit.py
Added insertions/deletions/lines properities for easy commit info collection

From phrase the gitpython classgit.util.Stats(total, files) it could give the data we need
https://gitpython.readthedocs.io/en/stable/reference.html?highlight=stats.total#git.util.Stats



Patch:
diff --git a/pydriller/domain/commit.py b/pydriller/domain/commit.py
index f59921e..833d221 100644
--- a/pydriller/domain/commit.py
+++ b/pydriller/domain/commit.py
@@ -553,6 +553,42 @@ class Commit:
         """
         return len(self._c_object.parents) > 1
 
+    @property
+    def insertions(self) -> int:
+        """
+        Return the insertion lines of the commit.
+
+        :return: int insertion lines
+        """
+        return self._c_object.stats.total["insertions"]
+
+    @property
+    def deletions(self) -> int:
+        """
+        Return the deletion lines of the commit.
+
+        :return: int deletion lines
+        """
+        return self._c_object.stats.total["deletions"]
+
+    @property
+    def lines(self) -> int:
+        """
+        Return the total lines of the commit.
+
+        :return: int insertion + deletion lines
+        """
+        return self._c_object.stats.total["lines"]
+
+    @property
+    def files(self) -> int:
+        """
+        Return the modified files of the commit.
+
+        :return: int modified files number
+        """
+        return self._c_object.stats.total["files"]
+
     @property
     def modifications(self) -> List[Modification]:
         """


Test patch:
diff --git a/tests/test_git_repository.py b/tests/test_git_repository.py
index 7b834c8..a670e6d 100644
--- a/tests/test_git_repository.py
+++ b/tests/test_git_repository.py
@@ -72,6 +72,10 @@ def test_get_commit(repo: GitRepository):
     assert len(c.modifications) == 1
     assert c.msg == 'Ooops file2'
     assert c.in_main_branch is True
+    assert c.insertions == 4
+    assert c.deletions == 0
+    assert c.lines == 4
+    assert c.files == 1
 
 
 @pytest.mark.parametrize('repo', ['test-repos/detached_head/'], indirect=True)
Repository: ishepard/pydriller
Pull request #189 (2021-07-08 18:36:35)

Catch specific exception with malformed repo url
First of all thanks for the great library. I am currently developing a [streamlit][1] dashboard that computes some stats from a commit history.

It would be probably more sensible to catch a more specific exception when a user inputs wrongly formatted urls instead of a catch all `Exception`. Needless to say, if you find this relevant, feel free to rename the class as you best see fit.

Thanks again.

[1]: https://streamlit.io


Patch:
diff --git a/pydriller/repository.py b/pydriller/repository.py
index ddccc3b..f0d3dba 100644
--- a/pydriller/repository.py
+++ b/pydriller/repository.py
@@ -271,6 +271,11 @@ class Repository:
             last_suffix_index = len(url)
 
         if last_slash_index < 0 or last_suffix_index <= last_slash_index:
-            raise Exception("Badly formatted url {}".format(url))
+            raise MalformedUrl("Badly formatted url {}".format(url))
 
         return url[last_slash_index + 1:last_suffix_index]
+
+
+class MalformedUrl(Exception):
+    def __init__(self, message):
+        super().__init__(message)


Test patch:
diff --git a/tests/test_repository_mining.py b/tests/test_repository_mining.py
index 5e347e6..585148f 100644
--- a/tests/test_repository_mining.py
+++ b/tests/test_repository_mining.py
@@ -5,6 +5,7 @@ import pytest
 import sys
 
 from pydriller import Repository, Git
+from pydriller.repository import MalformedUrl
 
 logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s',
                     level=logging.INFO)
@@ -40,6 +41,12 @@ def test_badly_formatted_repo_url():
         list(Repository(path_to_repo=set('repo')).traverse_commits())
 
 
+# It should fail when URL is malformed
+def test_malformed_url():
+    with pytest.raises(MalformedUrl):
+        list(Repository("https://badurl.git/").traverse_commits())
+
+
 @pytest.mark.parametrize('repo,expected', [
     ("test-repos/small_repo", 5)
 ], indirect=['repo'])
Repository: ishepard/pydriller
Pull request #240 (2022-12-04 05:22:49)

enhance method _get_repo_name_from_url to handle case when .git is part of the repo name
Related issue #236 


Patch:
diff --git a/pydriller/repository.py b/pydriller/repository.py
index 8a663d6..486c03c 100644
--- a/pydriller/repository.py
+++ b/pydriller/repository.py
@@ -272,13 +272,18 @@ class Repository:
     @staticmethod
     def _get_repo_name_from_url(url: str) -> str:
         last_slash_index = url.rfind("/")
-        last_suffix_index = url.rfind(".git")
-        if last_suffix_index < 0:
-            last_suffix_index = len(url)
+        len_url = len(url)
 
-        if last_slash_index < 0 or last_suffix_index <= last_slash_index:
+        if last_slash_index < 0 or last_slash_index >= len_url - 1:
             raise MalformedUrl(f"Badly formatted url {url}")
 
+        last_dot_index = url.rfind(".")
+
+        if url[last_dot_index:] == ".git":
+            last_suffix_index = last_dot_index
+        else:
+            last_suffix_index = len_url
+
         return url[last_slash_index + 1:last_suffix_index]
 
 


Test patch:
diff --git a/tests/test_repository_mining.py b/tests/test_repository_mining.py
index 0e9b5aa..b0e5efa 100644
--- a/tests/test_repository_mining.py
+++ b/tests/test_repository_mining.py
@@ -269,6 +269,25 @@ def test_projectname_multiple_repos_remote():
         assert commit.project_name == 'pydriller'
 
 
+def test_get_repo_name_from_url():
+    # with .git in the middle of the name
+    url_set_a = [
+        "https://github.com/academicpages/academicpages.github.io",
+        "https://github.com/academicpages/academicpages.github.io.git",
+    ]
+
+    url_set_b = [
+        "https://github.com/ishepard/pydriller",
+        "https://github.com/ishepard/pydriller.git",
+    ]
+
+    for url in url_set_a:
+        assert Repository._get_repo_name_from_url(url) == "academicpages.github.io"
+
+    for url in url_set_b:
+        assert Repository._get_repo_name_from_url(url) == "pydriller"
+
+
 @pytest.mark.skipif(sys.version_info < (3, 8) and sys.platform == "win32", reason="requires Python3.8 or greater on Windows")
 def test_deletion_remotes():
     repos = [
Repository: ishepard/pydriller
Pull request #286 (2023-11-14 11:48:15)

Add diff function in git.py
This commit adds a diff function in git.py, which takes two commit ids and outputs a list of modified files.
Related issue: #278 


Patch:
diff --git a/pydriller/git.py b/pydriller/git.py
index b7a6d37..2576e35 100644
--- a/pydriller/git.py
+++ b/pydriller/git.py
@@ -244,6 +244,17 @@ class Git:
         return self._calculate_last_commits(commit, modifications,
                                             hashes_to_ignore_path)
 
+    def diff(self, from_commit_id: str, to_commit_id: str) -> List[ModifiedFile]:
+        from_commit = self.repo.commit(from_commit_id)
+        to_commit = self.repo.commit(to_commit_id)
+        diff_index = from_commit.diff(
+            other=to_commit,
+            paths=None,
+            create_patch=True)
+
+        modified_files_list = [ModifiedFile(diff=diff) for diff in diff_index]
+        return modified_files_list
+
     def _calculate_last_commits(self, commit: Commit,
                                 modifications: List[ModifiedFile],
                                 hashes_to_ignore_path: Optional[str] = None) \


Test patch:
diff --git a/tests/test_git.py b/tests/test_git.py
index 5302611..08a395c 100644
--- a/tests/test_git.py
+++ b/tests/test_git.py
@@ -500,3 +500,21 @@ def test_get_commits_last_modified_lines_hyper_blame_with_renaming(repo: Git):
         'A.java']
     assert '9568d20856728304ab0b4d2d02fb9e81d0e5156d' in buggy_commits[
         'H.java']
+
+
+@pytest.mark.parametrize('repo', ["test-repos/diff"], indirect=True)
+def test_diff_function_in_git(repo: Git):
+    from_commit_id = "9e9473d5ca310b7663e9df93c402302b6b7f24aa"
+    to_commit_id = "b267a14e0503fdac36d280422f16360d1f661f12"
+    modied_files = repo.diff(from_commit_id, to_commit_id)
+
+    diff = modied_files[0].diff
+
+    assert len(modied_files) == 1
+    assert "@@ -107,7 +107,7 @@ public class GitRepository implements SCM {" in diff
+    assert "     }" in diff
+    assert "     public static SCMRepository completelyNewName(String path) {" in diff
+    assert "-        return allProjectsIn(path, false);" in diff
+    assert "+        return new GitRepository(path).info();" in diff
+    assert "     }" in diff
+    assert "     public static SCMRepository singleProject(String path, boolean singleParentOnly) {" in diff
Repository: Stebalien/tempfile
Pull request #159 (2021-12-21 19:31:37)

Add method for manually creating TempPaths


Patch:
diff --git a/src/file/mod.rs b/src/file/mod.rs
index 41e5eed..b859ced 100644
--- a/src/file/mod.rs
+++ b/src/file/mod.rs
@@ -351,6 +351,18 @@ impl TempPath {
             }),
         }
     }
+
+    /// Create a new TempPath from an existing path. This can be done even if no
+    /// file exists at the given path.
+    ///
+    /// This is mostly useful for interacting with libraries and external
+    /// components that provide files to be consumed or expect a path with no
+    /// existing file to be given.
+    pub fn from_path(path: impl Into<PathBuf>) -> Self {
+        Self {
+            path: path.into().into_boxed_path(),
+        }
+    }
 }
 
 impl fmt::Debug for TempPath {


Test patch:
diff --git a/tests/namedtempfile.rs b/tests/namedtempfile.rs
index a489d56..d2c7da2 100644
--- a/tests/namedtempfile.rs
+++ b/tests/namedtempfile.rs
@@ -1,10 +1,11 @@
 #![deny(rust_2018_idioms)]
 
 use std::env;
+use std::ffi::{OsStr, OsString};
 use std::fs::File;
 use std::io::{Read, Seek, SeekFrom, Write};
-use std::path::Path;
-use tempfile::{Builder, NamedTempFile};
+use std::path::{Path, PathBuf};
+use tempfile::{tempdir, Builder, NamedTempFile, TempPath};
 
 fn exists<P: AsRef<Path>>(path: P) -> bool {
     std::fs::metadata(path.as_ref()).is_ok()
@@ -216,6 +217,50 @@ fn test_temppath_persist_noclobber() {
     std::fs::remove_file(&persist_path).unwrap();
 }
 
+#[test]
+fn temp_path_from_existing() {
+    let tmp_dir = tempdir().unwrap();
+    let tmp_file_path_1 = tmp_dir.path().join("testfile1");
+    let tmp_file_path_2 = tmp_dir.path().join("testfile2");
+
+    File::create(&tmp_file_path_1).unwrap();
+    assert!(tmp_file_path_1.exists(), "Test file 1 hasn't been created");
+
+    File::create(&tmp_file_path_2).unwrap();
+    assert!(tmp_file_path_2.exists(), "Test file 2 hasn't been created");
+
+    let tmp_path = TempPath::from_path(&tmp_file_path_1);
+    assert!(
+        tmp_file_path_1.exists(),
+        "Test file has been deleted before dropping TempPath"
+    );
+
+    drop(tmp_path);
+    assert!(
+        !tmp_file_path_1.exists(),
+        "Test file exists after dropping TempPath"
+    );
+    assert!(
+        tmp_file_path_2.exists(),
+        "Test file 2 has been deleted before dropping TempDir"
+    );
+}
+
+#[test]
+#[allow(unreachable_code)]
+fn temp_path_from_argument_types() {
+    // This just has to compile
+    return;
+
+    TempPath::from_path("");
+    TempPath::from_path(String::new());
+    TempPath::from_path(OsStr::new(""));
+    TempPath::from_path(OsString::new());
+    TempPath::from_path(Path::new(""));
+    TempPath::from_path(PathBuf::new());
+    TempPath::from_path(PathBuf::new().into_boxed_path());
+}
+
 #[test]
 fn test_write_after_close() {
     let path = NamedTempFile::new().unwrap().into_temp_path();