Browse Source

Added check_git_version helper

Not yet used.

Bug: b/360206460
Change-Id: I233cdc17ec17d477ec78024edca2b87417a92258
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5869272
Reviewed-by: Josip Sokcevic <sokcevic@chromium.org>
Reviewed-by: Yiwei Zhang <yiwzhang@google.com>
Commit-Queue: Anne Redulla <aredulla@google.com>
Anne Redulla 11 months ago
parent
commit
795b5b6a20
2 changed files with 94 additions and 3 deletions
  1. 40 3
      git_common.py
  2. 54 0
      tests/git_common_test.py

+ 40 - 3
git_common.py

@@ -81,6 +81,9 @@ def win_find_git():
 
 
 GIT_EXE = 'git' if not IS_WIN else win_find_git()
 GIT_EXE = 'git' if not IS_WIN else win_find_git()
 
 
+# The recommended minimum version of Git, as (<major>, <minor>, <patch>).
+GIT_MIN_VERSION = (2, 26, 0)
+
 FREEZE = 'FREEZE'
 FREEZE = 'FREEZE'
 FREEZE_SECTIONS = {'indexed': 'soft', 'unindexed': 'mixed'}
 FREEZE_SECTIONS = {'indexed': 'soft', 'unindexed': 'mixed'}
 FREEZE_MATCHER = re.compile(r'%s.(%s)' % (FREEZE, '|'.join(FREEZE_SECTIONS)))
 FREEZE_MATCHER = re.compile(r'%s.(%s)' % (FREEZE, '|'.join(FREEZE_SECTIONS)))
@@ -1195,6 +1198,39 @@ def upstream(branch):
         return None
         return None
 
 
 
 
+def check_git_version(
+        min_version: Tuple[int] = GIT_MIN_VERSION) -> Optional[str]:
+    """Checks whether git is installed, and its version meets the recommended
+    minimum version, which defaults to GIT_MIN_VERSION if not specified.
+
+    Returns:
+        - the remediation action to take.
+    """
+    min_tag = '.'.join(str(x) for x in min_version)
+    if shutil.which(GIT_EXE) is None:
+        # git command was not found.
+        return ('git command not found.\n'
+                f'Please install version >={min_tag} of git.\n'
+                'See instructions at\n'
+                'https://git-scm.com/book/en/v2/Getting-Started-Installing-Git')
+
+    if meets_git_version(min_version):
+        # git version is sufficient; no remediation action necessary.
+        return None
+
+    # git is installed but older than the recommended version.
+    tag = '.'.join(str(x) for x in get_git_version()) or 'unknown'
+    return ('git update is recommended.\n'
+            f'Installed git version is {tag};\n'
+            f'depot_tools recommends version {min_tag} or later.')
+
+
+def meets_git_version(min_version: Tuple[int]) -> bool:
+    """Returns whether the current git version meets the minimum specified."""
+    return get_git_version() >= min_version
+
+
+@functools.lru_cache(maxsize=1)
 def get_git_version():
 def get_git_version():
     """Returns a tuple that contains the numeric components of the current git
     """Returns a tuple that contains the numeric components of the current git
     version."""
     version."""
@@ -1204,9 +1240,10 @@ def get_git_version():
 
 
 def _extract_git_tuple(version_string):
 def _extract_git_tuple(version_string):
     version_match = re.search(r'(\d+.)+(\d+)', version_string)
     version_match = re.search(r'(\d+.)+(\d+)', version_string)
-    version = version_match.group() if version_match else ''
-
-    return tuple(int(x) for x in version.split('.'))
+    if version_match:
+        version = version_match.group()
+        return tuple(int(x) for x in version.split('.'))
+    return tuple()
 
 
 
 
 def get_num_commits(branch):
 def get_num_commits(branch):

+ 54 - 0
tests/git_common_test.py

@@ -1141,11 +1141,65 @@ class GitTestUtilsTest(git_test_utils.GitRepoReadOnlyTestBase):
             self.repo.show_commit('C', format_string='%cn %ce %ci'))
             self.repo.show_commit('C', format_string='%cn %ce %ci'))
 
 
 
 
+class CheckGitVersionTest(GitCommonTestBase):
+
+    def setUp(self):
+        self.addCleanup(self.gc.get_git_version.cache_clear)
+
+    @mock.patch('shutil.which')
+    def testGitNotInstalled(self, mockWhich):
+        mockWhich.return_value = None
+
+        recommendation = self.gc.check_git_version()
+        self.assertIsNotNone(recommendation)
+        self.assertTrue('Please install' in recommendation)
+
+        mockWhich.assert_called_once()
+
+    @mock.patch('shutil.which')
+    @mock.patch('git_common.run')
+    def testGitOldVersion(self, mockRun, mockWhich):
+        mockWhich.return_value = '/example/bin/git'
+        mockRun.return_value = 'git version 2.2.40-abc'
+
+        recommendation = self.gc.check_git_version()
+        self.assertIsNotNone(recommendation)
+        self.assertTrue('update is recommended' in recommendation)
+
+        mockWhich.assert_called_once()
+        mockRun.assert_called_once()
+
+    @mock.patch('shutil.which')
+    @mock.patch('git_common.run')
+    def testGitSufficientVersion(self, mockRun, mockWhich):
+        mockWhich.return_value = '/example/bin/git'
+        mockRun.return_value = 'git version 2.30.1.456'
+
+        self.assertIsNone(self.gc.check_git_version())
+
+        mockWhich.assert_called_once()
+        mockRun.assert_called_once()
+
+    @mock.patch('shutil.which')
+    @mock.patch('git_common.run')
+    def testHandlesErrorGettingVersion(self, mockRun, mockWhich):
+        mockWhich.return_value = '/example/bin/git'
+        mockRun.return_value = 'Error running git version'
+
+        recommendation = self.gc.check_git_version()
+        self.assertIsNotNone(recommendation)
+        self.assertTrue('update is recommended' in recommendation)
+
+        mockWhich.assert_called_once()
+        mockRun.assert_called_once()
+
+
 class WarnSubmoduleTest(unittest.TestCase):
 class WarnSubmoduleTest(unittest.TestCase):
     def setUp(self):
     def setUp(self):
         import git_common
         import git_common
         self.warn_submodule = git_common.warn_submodule
         self.warn_submodule = git_common.warn_submodule
         mock.patch('sys.stdout', StringIO()).start()
         mock.patch('sys.stdout', StringIO()).start()
+        self.addCleanup(mock.patch.stopall)
 
 
     def testWarnFSMonitorOldVersion(self):
     def testWarnFSMonitorOldVersion(self):
         mock.patch('git_common.is_fsmonitor_enabled', lambda: True).start()
         mock.patch('git_common.is_fsmonitor_enabled', lambda: True).start()