waylan: Apparently [unittest.TestCase.assertLogs][1] was added in Python 3.4. A PY2.7 backport can be found [here]. The [Unittest2][3] lib also contains a backport, however, I don;t see the need for that entire lib for one feature.

There is also the [testfixtures][4] lib, which includes some [logging capture tools][5], along with a bunch of other stuff that could simplify a bunch of our tests.

[1]: https://docs.python.org/3.4/library/unittest.html#unittest.TestCase.assertLogs
[2]: https://stackoverflow.com/a/29938398/866026
[3]: https://pypi.python.org/pypi/unittest2
[4]: http://testfixtures.readthedocs.io/en/latest/index.html
[5]: http://testfixtures.readthedocs.io/en/latest/logging.html
diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md
index 6f0f3518..7afca02a 100644
--- a/docs/about/release-notes.md
+++ b/docs/about/release-notes.md
@@ -66,6 +66,7 @@ authors should review how [search and themes] interact.
 
 ### Other Changes and Additions to Development Version
 
+* Add MkDocs version check to gh-deploy script (#640).
 * Improve Markdown extension error messages. (#782).
 * Drop official support for Python 3.3 and set `tornado>=5.0` (#1427).
 * Add support for GitLab edit links (#1435).
diff --git a/mkdocs/__main__.py b/mkdocs/__main__.py
index 8c231180..a3f7cdae 100644
--- a/mkdocs/__main__.py
+++ b/mkdocs/__main__.py
@@ -89,6 +89,7 @@ remote_branch_help = ("The remote branch to commit to for Github Pages. This "
 remote_name_help = ("The remote name to commit to for Github Pages. This "
                     "overrides the value specified in config")
 force_help = "Force the push to the repository."
+ignore_version_help = "Ignore check that build is not being deployed with an older version of MkDocs."
 
 pgk_dir = os.path.dirname(os.path.abspath(__file__))
 
@@ -172,8 +173,9 @@ def build_command(clean, config_file, strict, theme, theme_dir, site_dir):
 @click.option('-b', '--remote-branch', help=remote_branch_help)
 @click.option('-r', '--remote-name', help=remote_name_help)
 @click.option('--force', is_flag=True, help=force_help)
+@click.option('--ignore-version', is_flag=True, help=ignore_version_help)
 @common_options
-def gh_deploy_command(config_file, clean, message, remote_branch, remote_name, force):
+def gh_deploy_command(config_file, clean, message, remote_branch, remote_name, force, ignore_version):
     """Deploy your documentation to GitHub Pages"""
     try:
         cfg = config.load_config(
@@ -182,7 +184,7 @@ def gh_deploy_command(config_file, clean, message, remote_branch, remote_name, f
             remote_name=remote_name
         )
         build.build(cfg, dirty=not clean)
-        gh_deploy.gh_deploy(cfg, message=message, force=force)
+        gh_deploy.gh_deploy(cfg, message=message, force=force, ignore_version=ignore_version)
     except exceptions.ConfigurationError as e:  # pragma: no cover
         # Avoid ugly, unhelpful traceback
         raise SystemExit('\n' + str(e))
diff --git a/mkdocs/commands/gh_deploy.py b/mkdocs/commands/gh_deploy.py
index ccadab6d..b3d0548f 100644
--- a/mkdocs/commands/gh_deploy.py
+++ b/mkdocs/commands/gh_deploy.py
@@ -2,6 +2,8 @@ from __future__ import unicode_literals
 import logging
 import subprocess
 import os
+import re
+from pkg_resources import parse_version
 
 import mkdocs
 from mkdocs.utils import ghp_import
@@ -49,20 +51,49 @@ def _get_remote_url(remote_name):
     return host, path
 
 
-def gh_deploy(config, message=None, force=False):
+def _check_version(branch):
+
+    proc = subprocess.Popen(['git', 'show', '-s', '--format=%s', 'refs/heads/{}'.format(branch)],
+                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+    stdout, _ = proc.communicate()
+    msg = stdout.decode('utf-8').strip()
+    m = re.search(r'\d+(\.\d+)+', msg, re.X | re.I)
+    previousv = parse_version(m.group()) if m else None
+    currentv = parse_version(mkdocs.__version__)
+    if not previousv:
+        log.warn('Version check skipped: No version specificed in previous deployment.')
+    elif currentv > previousv:
+        log.info(
+            'Previous deployment was done with MkDocs version {}; '
+            'you are deploying with a newer version ({})'.format(previousv, currentv)
+        )
+    elif currentv < previousv:
+        log.error(
+            'Deployment terminated: Previous deployment was made with MkDocs version {}; '
+            'you are attempting to deploy with an older version ({}). Use --ignore-version '
+            'to deploy anyway.'.format(previousv, currentv)
+        )
+        raise SystemExit(1)
+
+
+def gh_deploy(config, message=None, force=False, ignore_version=False):
 
     if not _is_cwd_git_repo():
         log.error('Cannot deploy - this directory does not appear to be a git '
                   'repository')
 
+    remote_branch = config['remote_branch']
+    remote_name = config['remote_name']
+
+    if not ignore_version:
+        _check_version(remote_branch)
+
     if message is None:
         message = default_message
     sha = _get_current_sha(os.path.dirname(config.config_file_path))
     message = message.format(version=mkdocs.__version__, sha=sha)
 
-    remote_branch = config['remote_branch']
-    remote_name = config['remote_name']
-
     log.info("Copying '%s' to '%s' branch and pushing to GitHub.",
              config['site_dir'], config['remote_branch'])
 

Warn users if they are using an old/different version
pip has a nice feature where they warn users if they are using an old version and prompt them to update. I think it would be neat to add something like this. The code they use for it is here: https://github.com/pypa/pip/blob/7.0.3/pip/utils/outdated.py#L95

diff --git a/mkdocs/tests/base.py b/mkdocs/tests/base.py
index 20ba63ec..a17fc2e0 100644
--- a/mkdocs/tests/base.py
+++ b/mkdocs/tests/base.py
@@ -2,9 +2,14 @@ from __future__ import unicode_literals
 import textwrap
 import markdown
 import os
+import logging
+import collections
+import unittest
+
 
 from mkdocs import toc
 from mkdocs import config
+from mkdocs import utils
 
 
 def dedent(text):
@@ -37,3 +42,97 @@ def load_config(**cfg):
     errors_warnings = conf.validate()
     assert(errors_warnings == ([], [])), errors_warnings
     return conf
+
+
+# Backport unittest.TestCase.assertLogs for Python 2.7
+# see https://github.com/python/cpython/blob/3.6/Lib/unittest/case.py
+
+if not utils.PY3:
+    _LoggingWatcher = collections.namedtuple("_LoggingWatcher",
+                                             ["records", "output"])
+
+    class _CapturingHandler(logging.Handler):
+        """
+        A logging handler capturing all (raw and formatted) logging output.
+        """
+
+        def __init__(self):
+            logging.Handler.__init__(self)
+            self.watcher = _LoggingWatcher([], [])
+
+        def flush(self):
+            pass
+
+        def emit(self, record):
+            self.watcher.records.append(record)
+            msg = self.format(record)
+            self.watcher.output.append(msg)
+
+    class _AssertLogsContext(object):
+        """A context manager used to implement TestCase.assertLogs()."""
+
+        LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s"
+
+        def __init__(self, test_case, logger_name, level):
+            self.test_case = test_case
+            self.logger_name = logger_name
+            if level:
+                self.level = logging._levelNames.get(level, level)
+            else:
+                self.level = logging.INFO
+            self.msg = None
+
+        def __enter__(self):
+            if isinstance(self.logger_name, logging.Logger):
+                logger = self.logger = self.logger_name
+            else:
+                logger = self.logger = logging.getLogger(self.logger_name)
+            formatter = logging.Formatter(self.LOGGING_FORMAT)
+            handler = _CapturingHandler()
+            handler.setFormatter(formatter)
+            self.watcher = handler.watcher
+            self.old_handlers = logger.handlers[:]
+            self.old_level = logger.level
+            self.old_propagate = logger.propagate
+            logger.handlers = [handler]
+            logger.setLevel(self.level)
+            logger.propagate = False
+            return handler.watcher
+
+        def __exit__(self, exc_type, exc_value, tb):
+            self.logger.handlers = self.old_handlers
+            self.logger.propagate = self.old_propagate
+            self.logger.setLevel(self.old_level)
+            if exc_type is not None:
+                # let unexpected exceptions pass through
+                return False
+            if len(self.watcher.records) == 0:
+                self._raiseFailure(
+                    "no logs of level {} or higher triggered on {}"
+                    .format(logging.getLevelName(self.level), self.logger.name))
+
+        def _raiseFailure(self, standardMsg):
+            msg = self.test_case._formatMessage(self.msg, standardMsg)
+            raise self.test_case.failureException(msg)
+
+    class LogTestCase(unittest.TestCase):
+        def assertLogs(self, logger=None, level=None):
+            """Fail unless a log message of level *level* or higher is emitted
+            on *logger_name* or its children.  If omitted, *level* defaults to
+            INFO and *logger* defaults to the root logger.
+            This method must be used as a context manager, and will yield
+            a recording object with two attributes: `output` and `records`.
+            At the end of the context manager, the `output` attribute will
+            be a list of the matching formatted log messages and the
+            `records` attribute will be a list of the corresponding LogRecord
+            objects.
+            Example::
+                with self.assertLogs('foo', level='INFO') as cm:
+                    logging.getLogger('foo').info('first message')
+                    logging.getLogger('foo.bar').error('second message')
+                self.assertEqual(cm.output, ['INFO:foo:first message',
+                                             'ERROR:foo.bar:second message'])
+            """
+            return _AssertLogsContext(self, logger, level)
+else:
+    LogTestCase = unittest.TestCase
diff --git a/mkdocs/tests/cli_tests.py b/mkdocs/tests/cli_tests.py
index 57f12845..939096f7 100644
--- a/mkdocs/tests/cli_tests.py
+++ b/mkdocs/tests/cli_tests.py
@@ -346,6 +346,8 @@ class CLITests(unittest.TestCase):
         self.assertEqual(g_kwargs['message'], None)
         self.assertTrue('force' in g_kwargs)
         self.assertEqual(g_kwargs['force'], False)
+        self.assertTrue('ignore_version' in g_kwargs)
+        self.assertEqual(g_kwargs['ignore_version'], False)
         self.assertEqual(mock_build.call_count, 1)
         b_args, b_kwargs = mock_build.call_args
         self.assertTrue('dirty' in b_kwargs)
@@ -471,3 +473,19 @@ class CLITests(unittest.TestCase):
         self.assertEqual(g_kwargs['force'], True)
         self.assertEqual(mock_build.call_count, 1)
         self.assertEqual(mock_load_config.call_count, 1)
+
+    @mock.patch('mkdocs.config.load_config', autospec=True)
+    @mock.patch('mkdocs.commands.build.build', autospec=True)
+    @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
+    def test_gh_deploy_ognore_version(self, mock_gh_deploy, mock_build, mock_load_config):
+
+        result = self.runner.invoke(
+            cli.cli, ['gh-deploy', '--ignore-version'], catch_exceptions=False)
+
+        self.assertEqual(result.exit_code, 0)
+        self.assertEqual(mock_gh_deploy.call_count, 1)
+        g_args, g_kwargs = mock_gh_deploy.call_args
+        self.assertTrue('ignore_version' in g_kwargs)
+        self.assertEqual(g_kwargs['ignore_version'], True)
+        self.assertEqual(mock_build.call_count, 1)
+        self.assertEqual(mock_load_config.call_count, 1)
diff --git a/mkdocs/tests/gh_deploy_tests.py b/mkdocs/tests/gh_deploy_tests.py
index 7b8793bc..495a6e90 100644
--- a/mkdocs/tests/gh_deploy_tests.py
+++ b/mkdocs/tests/gh_deploy_tests.py
@@ -3,8 +3,9 @@ from __future__ import unicode_literals
 import unittest
 import mock
 
-from mkdocs.tests.base import load_config
+from mkdocs.tests.base import load_config, LogTestCase
 from mkdocs.commands import gh_deploy
+from mkdocs import __version__
 
 
 class TestGitHubDeploy(unittest.TestCase):
@@ -99,6 +100,32 @@ class TestGitHubDeploy(unittest.TestCase):
         )
         gh_deploy.gh_deploy(config)
 
+    @mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True)
+    @mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas')
+    @mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=(None, None))
+    @mock.patch('mkdocs.commands.gh_deploy._check_version')
+    @mock.patch('mkdocs.commands.gh_deploy.ghp_import.ghp_import', return_value=(True, ''))
+    def test_deploy_ignore_version_default(self, mock_import, check_version, get_remote, get_sha, is_repo):
+
+        config = load_config(
+            remote_branch='test',
+        )
+        gh_deploy.gh_deploy(config)
+        check_version.assert_called_once()
+
+    @mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True)
+    @mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas')
+    @mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=(None, None))
+    @mock.patch('mkdocs.commands.gh_deploy._check_version')
+    @mock.patch('mkdocs.commands.gh_deploy.ghp_import.ghp_import', return_value=(True, ''))
+    def test_deploy_ignore_version(self, mock_import, check_version, get_remote, get_sha, is_repo):
+
+        config = load_config(
+            remote_branch='test',
+        )
+        gh_deploy.gh_deploy(config, ignore_version=True)
+        check_version.assert_not_called()
+
     @mock.patch('mkdocs.utils.ghp_import.ghp_import')
     @mock.patch('mkdocs.commands.gh_deploy.log')
     def test_deploy_error(self, mock_log, mock_import):
@@ -112,3 +139,43 @@ class TestGitHubDeploy(unittest.TestCase):
         self.assertRaises(SystemExit, gh_deploy.gh_deploy, config)
         mock_log.error.assert_called_once_with('Failed to deploy to GitHub with error: \n%s',
                                                error_string)
+
+
+class TestGitHubDeployLogs(LogTestCase):
+
+    @mock.patch('subprocess.Popen')
+    def test_mkdocs_newer(self, mock_popeno):
+
+        mock_popeno().communicate.return_value = (b'Deployed 12345678 with MkDocs version: 0.1.2\n', b'')
+
+        with self.assertLogs('mkdocs', level='INFO') as cm:
+            gh_deploy._check_version('gh-pages')
+        self.assertEqual(
+            cm.output, ['INFO:mkdocs.commands.gh_deploy:Previous deployment was done with MkDocs '
+                        'version 0.1.2; you are deploying with a newer version ({})'.format(__version__)]
+        )
+
+    @mock.patch('subprocess.Popen')
+    def test_mkdocs_older(self, mock_popeno):
+
+        mock_popeno().communicate.return_value = (b'Deployed 12345678 with MkDocs version: 10.1.2\n', b'')
+
+        with self.assertLogs('mkdocs', level='ERROR') as cm:
+            self.assertRaises(SystemExit, gh_deploy._check_version, 'gh-pages')
+        self.assertEqual(
+            cm.output, ['ERROR:mkdocs.commands.gh_deploy:Deployment terminated: Previous deployment was made with '
+                        'MkDocs version 10.1.2; you are attempting to deploy with an older version ({}). Use '
+                        '--ignore-version to deploy anyway.'.format(__version__)]
+        )
+
+    @mock.patch('subprocess.Popen')
+    def test_version_unknown(self, mock_popeno):
+
+        mock_popeno().communicate.return_value = (b'No version specified\n', b'')
+
+        with self.assertLogs('mkdocs', level='WARNING') as cm:
+            gh_deploy._check_version('gh-pages')
+        self.assertEqual(
+            cm.output,
+            ['WARNING:mkdocs.commands.gh_deploy:Version check skipped: No version specificed in previous deployment.']
+        )

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-mock mock
apt-get update apt-get install -y gcc
3.9
requirements/project.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
click==8.1.8
exceptiongroup==1.2.2
importlib_metadata==8.6.1
iniconfig==2.1.0
Jinja2==3.1.6
livereload==2.7.1
Markdown==3.7
MarkupSafe==3.0.2
mdx-gh-links==0.4
-e git+https://github.com/mkdocs/mkdocs.git@ee37665418a8c1a20eff6041fe17d0d107dde07c#egg=mkdocs
mock==5.2.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
pytest-mock==3.14.0
PyYAML==6.0.2
tomli==2.2.1
tornado==6.4.2
zipp==3.21.0

name: mkdocs
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - click==8.1.8
      - exceptiongroup==1.2.2
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - jinja2==3.1.6
      - livereload==2.7.1
      - markdown==3.7
      - markupsafe==3.0.2
      - mdx-gh-links==0.4
      - mock==5.2.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-mock==3.14.0
      - pyyaml==6.0.2
      - tomli==2.2.1
      - tornado==6.4.2
      - zipp==3.21.0
prefix: /opt/conda/envs/mkdocs

mkdocs/tests/cli_tests.py::CLITests::test_gh_deploy_defaults mkdocs/tests/cli_tests.py::CLITests::test_gh_deploy_ognore_version mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_deploy_ignore_version mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_deploy_ignore_version_default mkdocs/tests/gh_deploy_tests.py::TestGitHubDeployLogs::test_mkdocs_newer mkdocs/tests/gh_deploy_tests.py::TestGitHubDeployLogs::test_mkdocs_older mkdocs/tests/gh_deploy_tests.py::TestGitHubDeployLogs::test_version_unknown
mkdocs/tests/cli_tests.py::CLITests::test_build_clean mkdocs/tests/cli_tests.py::CLITests::test_build_config_file mkdocs/tests/cli_tests.py::CLITests::test_build_defaults mkdocs/tests/cli_tests.py::CLITests::test_build_dirty mkdocs/tests/cli_tests.py::CLITests::test_build_quiet mkdocs/tests/cli_tests.py::CLITests::test_build_site_dir mkdocs/tests/cli_tests.py::CLITests::test_build_strict mkdocs/tests/cli_tests.py::CLITests::test_build_theme mkdocs/tests/cli_tests.py::CLITests::test_build_theme_dir mkdocs/tests/cli_tests.py::CLITests::test_build_verbose mkdocs/tests/cli_tests.py::CLITests::test_gh_deploy_clean mkdocs/tests/cli_tests.py::CLITests::test_gh_deploy_config_file mkdocs/tests/cli_tests.py::CLITests::test_gh_deploy_dirty mkdocs/tests/cli_tests.py::CLITests::test_gh_deploy_force mkdocs/tests/cli_tests.py::CLITests::test_gh_deploy_message mkdocs/tests/cli_tests.py::CLITests::test_gh_deploy_remote_branch mkdocs/tests/cli_tests.py::CLITests::test_gh_deploy_remote_name mkdocs/tests/cli_tests.py::CLITests::test_new mkdocs/tests/cli_tests.py::CLITests::test_serve_config_file mkdocs/tests/cli_tests.py::CLITests::test_serve_default mkdocs/tests/cli_tests.py::CLITests::test_serve_dev_addr mkdocs/tests/cli_tests.py::CLITests::test_serve_dirtyreload mkdocs/tests/cli_tests.py::CLITests::test_serve_livereload mkdocs/tests/cli_tests.py::CLITests::test_serve_no_livereload mkdocs/tests/cli_tests.py::CLITests::test_serve_strict mkdocs/tests/cli_tests.py::CLITests::test_serve_theme mkdocs/tests/cli_tests.py::CLITests::test_serve_theme_dir mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_deploy mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_deploy_error mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_deploy_hostname mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_deploy_no_cname mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_get_current_sha mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_get_remote_url_enterprise mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_get_remote_url_http mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_get_remote_url_ssh mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_is_cwd_git_repo mkdocs/tests/gh_deploy_tests.py::TestGitHubDeploy::test_is_cwd_not_git_repo
mkdocs/commands/gh_deploy.py docs/about/release-notes.md mkdocs/__main__.py
mkdocs/commands/gh_deploy.py docs/about/release-notes.md mkdocs/__main__.py