Repository: beeware/briefcase
Pull request #1915 (2024-07-15 20:27:11)

Verify the version of user's installed Git is acceptable
## Changes
- A user in [Discord](https://discord.com/channels/836455665257021440/836455665257021443/1260273576540377223) reported that `briefcase-template` could not be cloned and the issue ultimately stemmed from their dated version of Git.
- The version of Git is now verified to be at least v2.17.0.

## Notes
- Since Git's release notes are impenetrable insofar as what's meaningfully changing for users....I discovered through trial and error that Briefcase works as far back as v2.17.0 and begins failing at v2.16.6.
- Failure log using v2.16.6: [briefcase.2024_07_15-16_25_47.new.log](https://github.com/user-attachments/files/16239976/briefcase.2024_07_15-16_25_47.new.log)
- RE: https://github.com/beeware/briefcase/issues/1907


## PR Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
- [X] All new features have been tested
- [X] All new features have been documented
- [X] I have read the **CONTRIBUTING.md** file
- [X] I will abide by the code of conduct



Patch:
diff --git a/changes/1915.feature.rst b/changes/1915.feature.rst
new file mode 100644
index 00000000..4c245ee1
--- /dev/null
+++ b/changes/1915.feature.rst
@@ -0,0 +1,1 @@
+When the available version if Git is older than v2.17.0, an error message now prompts the user to upgrade their install of Git to proceed.
diff --git a/src/briefcase/integrations/git.py b/src/briefcase/integrations/git.py
index 55b83c59..976f5af0 100644
--- a/src/briefcase/integrations/git.py
+++ b/src/briefcase/integrations/git.py
@@ -10,6 +10,9 @@ class Git(Tool):
     name = "git"
     full_name = "Git"
 
+    MIN_VERSION = (2, 17, 0)
+    GIT_URL = "https://git-scm.com/"
+
     @classmethod
     def verify_install(cls, tools: ToolCache, **kwargs) -> ModuleType:
         """Verify if git is installed.
@@ -42,14 +45,14 @@ class Git(Tool):
             # for this.
             if tools.host_os == "Darwin":
                 raise BriefcaseCommandError(
-                    """\
+                    f"""\
 Briefcase requires git, but it is not installed. Xcode provides git; you should
 be shown a dialog prompting you to install Xcode and the Command Line Developer
 Tools. Select "Install" to install the Command Line Developer Tools.
 
 Alternatively, you can visit:
 
-    https://git-scm.com/
+    {cls.GIT_URL}
 
 to download and install git manually.
 
@@ -60,10 +63,10 @@ need to restart your terminal session.
 
             else:
                 raise BriefcaseCommandError(
-                    """\
+                    f"""\
 Briefcase requires git, but it is not installed (or is not on your PATH). Visit:
 
-    https://git-scm.com/
+    {cls.GIT_URL}
 
 to download and install git manually.
 
@@ -72,6 +75,16 @@ need to restart your terminal session.
 """
                 ) from e
 
+        installed_version = git.cmd.Git().version_info
+        if installed_version < cls.MIN_VERSION:
+            raise BriefcaseCommandError(
+                f"At least Git v{'.'.join(map(str, cls.MIN_VERSION))} is required; "
+                f"however, v{'.'.join(map(str, installed_version))} is installed.\n"
+                "\n"
+                f"Please update Git; downloads are available at {cls.GIT_URL}.",
+                skip_logfile=True,
+            )
+
         tools.logger.configure_stdlib_logging("git")
 
         tools.git = git


Test patch:
diff --git a/tests/integrations/git/test_Git__verify.py b/tests/integrations/git/test_Git__verify.py
index 0a8e6ae0..dceab6bf 100644
--- a/tests/integrations/git/test_Git__verify.py
+++ b/tests/integrations/git/test_Git__verify.py
@@ -1,9 +1,11 @@
 import logging
+from unittest.mock import PropertyMock
 
+import git
 import pytest
 
 from briefcase.console import LogLevel, RichLoggingHandler
-from briefcase.exceptions import UnsupportedHostError
+from briefcase.exceptions import BriefcaseCommandError, UnsupportedHostError
 from briefcase.integrations.git import Git
 
 
@@ -49,3 +51,31 @@ def test_git_stdlib_logging(mock_tools, logging_level, handler_expected):
 
     # reset handlers since they are persistent
     logging.getLogger("git").handlers.clear()
+
+
+@pytest.mark.parametrize("version", [(2, 17, 0), (2, 45, 2), (3, 0, 0)])
+def test_git_version_valid(mock_tools, version, monkeypatch):
+    """A valid Git version is accepted."""
+    monkeypatch.setattr(
+        git.cmd.Git,
+        "version_info",
+        PropertyMock(return_value=version),
+    )
+
+    Git.verify(mock_tools)
+
+
+@pytest.mark.parametrize("version", [(2, 16, 6), (2, 13, 2), (1, 0, 0)])
+def test_git_version_invalid(mock_tools, version, monkeypatch):
+    """An invalid Git version is rejected."""
+    monkeypatch.setattr(
+        git.cmd.Git,
+        "version_info",
+        PropertyMock(return_value=version),
+    )
+
+    with pytest.raises(
+        BriefcaseCommandError,
+        match=f"At least Git v2.17.0 is required; however, v{'.'.join(map(str, version))} is installed.",
+    ):
+        Git.verify(mock_tools)