Repository: mkdocs/mkdocs

Issue:
Reproducible Builds
So far, if one of the md file of my doc is modified, all the html files get modified, resulting in a large commit. Apprently the modification are essentialy random newlines.

Is there a way to avoid modifiyng every files when only in order not to flood commits when doc is modified ?

Thanks


Discussion:
This might be an issue with you git configuration. Are you on windows? Could be windows newlines being replaced by Unix newlines.

Also, you probably don't need to have generated artifacts like HTML under version control. You could .gitignore them as you can always rebuild them. 
I'm on linux

In my use case, I publish the doc from master/docs folder.


On Thu Dec 9, 2021 at 12:30 AM CET, Tim Vink wrote:
> This might be an issue with you git configuration. Are you on windows?
> Could be windows newlines being replaced by Unix newlines.
>
> Also, you probably don't need to have generated artifacts like HTML
> under version control. You could .gitignore them as you can always
> rebuild them.
>
> --
> You are receiving this because you authored the thread.
> Reply to this email directly or view it on GitHub:
> https://github.com/mkdocs/mkdocs/issues/2689#issuecomment-989311380


I haven't observed that this type of issue exists. I only saw that `sitemap.xml.gz` changes every time.
So, you'd have to provide some particular site as a reproducible example.  It could be a particular theme or plugin causing it. But I cannot guess it.
@oprypin The changing of the `sitemap.xml.gz` is a little strange. 

Is there a way to disable the generation of the gzipped sitemap?
Yes
And would you be so kind and tell me how?  I cannot find anything on https://www.mkdocs.org/user-guide/configuration/

Resolving patch:
diff --git a/mkdocs/commands/build.py b/mkdocs/commands/build.py
--- a/mkdocs/commands/build.py
+++ b/mkdocs/commands/build.py
@@ -112,7 +112,9 @@ def _build_theme_template(
             log.debug(f"Gzipping template: {template_name}")
             gz_filename = f'{output_path}.gz'
             with open(gz_filename, 'wb') as f:
-                timestamp = utils.get_build_timestamp()
+                timestamp = utils.get_build_timestamp(
+                    pages=[f.page for f in files.documentation_pages() if f.page is not None]
+                )
                 with gzip.GzipFile(
                     fileobj=f, filename=gz_filename, mode='wb', mtime=timestamp
                 ) as gz_buf:
diff --git a/mkdocs/structure/pages.py b/mkdocs/structure/pages.py
--- a/mkdocs/structure/pages.py
+++ b/mkdocs/structure/pages.py
@@ -42,7 +42,7 @@ def __init__(self, title: str | None, file: File, config: MkDocsConfig) -> None:
         self.next_page = None
         self.active = False
 
-        self.update_date = get_build_date()
+        self.update_date: str = get_build_date()
 
         self._set_canonical_url(config.get('site_url', None))
         self._set_edit_url(
diff --git a/mkdocs/utils/__init__.py b/mkdocs/utils/__init__.py
--- a/mkdocs/utils/__init__.py
+++ b/mkdocs/utils/__init__.py
@@ -44,18 +44,19 @@
 )
 
 
-def get_build_timestamp() -> int:
+def get_build_timestamp(*, pages: Collection[Page] | None = None) -> int:
     """
-    Returns the number of seconds since the epoch.
+    Returns the number of seconds since the epoch for the latest updated page.
 
-    Support SOURCE_DATE_EPOCH environment variable for reproducible builds.
-    See https://reproducible-builds.org/specs/source-date-epoch/
+    In reality this is just today's date because that's how pages' update time is populated.
     """
-    source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH')
-    if source_date_epoch is None:
-        return int(datetime.now(timezone.utc).timestamp())
-
-    return int(source_date_epoch)
+    if pages:
+        # Lexicographic comparison is OK for ISO date.
+        date_string = max(p.update_date for p in pages)
+        dt = datetime.fromisoformat(date_string)
+    else:
+        dt = get_build_datetime()
+    return int(dt.timestamp())
 
 
 def get_build_datetime() -> datetime:


Test patch:
diff --git a/mkdocs/tests/build_tests.py b/mkdocs/tests/build_tests.py
--- a/mkdocs/tests/build_tests.py
+++ b/mkdocs/tests/build_tests.py
@@ -233,7 +233,7 @@ def test_extra_context(self):
     def test_build_theme_template(self, mock_build_template, mock_write_file):
         cfg = load_config()
         env = cfg.theme.get_env()
-        build._build_theme_template('main.html', env, mock.Mock(), cfg, mock.Mock())
+        build._build_theme_template('main.html', env, Files([]), cfg, mock.Mock())
         mock_write_file.assert_called_once()
         mock_build_template.assert_called_once()
 
@@ -246,7 +246,7 @@ def test_build_sitemap_template(
     ):
         cfg = load_config(site_dir=site_dir)
         env = cfg.theme.get_env()
-        build._build_theme_template('sitemap.xml', env, mock.Mock(), cfg, mock.Mock())
+        build._build_theme_template('sitemap.xml', env, Files([]), cfg, mock.Mock())
         mock_write_file.assert_called_once()
         mock_build_template.assert_called_once()
         mock_gzip_gzipfile.assert_called_once()
@@ -257,7 +257,7 @@ def test_skip_missing_theme_template(self, mock_build_template, mock_write_file)
         cfg = load_config()
         env = cfg.theme.get_env()
         with self.assertLogs('mkdocs') as cm:
-            build._build_theme_template('missing.html', env, mock.Mock(), cfg, mock.Mock())
+            build._build_theme_template('missing.html', env, Files([]), cfg, mock.Mock())
         self.assertEqual(
             '\n'.join(cm.output),
             "WARNING:mkdocs.commands.build:Template skipped: 'missing.html' not found in theme directories.",
@@ -271,7 +271,7 @@ def test_skip_theme_template_empty_output(self, mock_build_template, mock_write_
         cfg = load_config()
         env = cfg.theme.get_env()
         with self.assertLogs('mkdocs') as cm:
-            build._build_theme_template('main.html', env, mock.Mock(), cfg, mock.Mock())
+            build._build_theme_template('main.html', env, Files([]), cfg, mock.Mock())
         self.assertEqual(
             '\n'.join(cm.output),
             "INFO:mkdocs.commands.build:Template skipped: 'main.html' generated empty output.",