This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch feature/scm-last-modification-date-module
in repository https://gitbox.apache.org/repos/asf/maven-doxia-sitetools.git

commit f90ee751dd13e743793b64098d36c9b4eac79829
Author: Konrad Windszus <[email protected]>
AuthorDate: Sun Mar 1 20:51:28 2026 +0100

    Expose SCM last modification date and author from Doxia source in
    Velocity
    
    The new Velocity property "scmModifiedDate" is receiving its value as
    Date.
    
    This closes #279
---
 doxia-site-model/src/main/mdo/site.mdo             |  68 +++++++--
 doxia-site-renderer/pom.xml                        |   1 +
 .../doxia/siterenderer/ContextCustomizer.java      |  34 +++++
 .../doxia/siterenderer/DefaultSiteRenderer.java    |  24 ++-
 .../doxia/siterenderer/SiteRenderingContext.java   |  22 +++
 .../doxia/siterenderer/sink/SiteRendererSink.java  |   1 +
 .../src/main/resources/site-renderer.properties    |   1 +
 .../src/main/resources/site-renderer_de.properties |   1 +
 doxia-site-renderer/src/site/apt/index.apt.vm      |   6 +-
 .../siterenderer/DefaultSiteRendererTest.java      |  11 +-
 .../doxia/siterenderer/LastModifiedVerifier.java   |  45 ++++++
 .../site-last-modified/markdown/lastmodified.md.vm |  21 +++
 .../src/test/resources/site-last-modified/site.xml |  36 +++++
 doxia-site-scm-context/pom.xml                     | 142 ++++++++++++++++++
 .../ScmAttributesContextCustomizer.java            | 161 +++++++++++++++++++++
 .../ScmAttributesContextCustomizerIT.java          |  87 +++++++++++
 .../ScmAttributesContextCustomizerTest.java        | 126 ++++++++++++++++
 .../site-last-modified/markdown/lastmodified.md.vm |  21 +++
 .../src/test/resources/site-last-modified/site.xml |  36 +++++
 pom.xml                                            |   6 +
 20 files changed, 833 insertions(+), 17 deletions(-)

diff --git a/doxia-site-model/src/main/mdo/site.mdo 
b/doxia-site-model/src/main/mdo/site.mdo
index 9d04432..6e77516 100644
--- a/doxia-site-model/src/main/mdo/site.mdo
+++ b/doxia-site-model/src/main/mdo/site.mdo
@@ -103,6 +103,15 @@ under the License.
           </association>
           <identifier>true</identifier>
         </field>
+        <field java.getter="false">
+          <name>modificationDate</name>
+          <description>Modify the date modified display properties. The date 
formatting is taken over from "publishDate".</description>
+          <version>2.1.0+</version>
+          <association>
+            <type>ModificationDate</type>
+          </association>
+          <identifier>true</identifier>
+        </field>
         <field java.getter="false">
           <name>version</name>
           <description>Modify the version published display 
properties.</description>
@@ -204,6 +213,19 @@ under the License.
         return publishDate == null;
     }
 
+    public ModificationDate getModificationDate()
+    {
+        if ( modificationDate == null )
+            return new ModificationDate();
+        else
+            return modificationDate;
+    }
+
+    public boolean isDefaultModificationDate()
+    {
+        return modificationDate == null;
+    }
+
     public Version getVersion()
     {
         if ( version == null )
@@ -335,18 +357,9 @@ under the License.
     </class>
 
     <class java.clone="deep">
-      <name>PublishDate</name>
-      <description>Modify display properties for date published.</description>
-      <version>1.0.0+</version>
+      <name>SiteDateFormat</name>
+      <description>Modify formatting properties for a date field.</description>
       <fields>
-        <field xml.attribute="true">
-          <name>position</name>
-          <description>Where to place the date published ("left", "right", 
"navigation-top", "navigation-bottom", "bottom" or "none" to hide 
it).</description>
-          <version>1.0.0+</version>
-          <type>String</type>
-          <identifier>true</identifier>
-          <defaultValue>left</defaultValue>
-        </field>
         <field xml.attribute="true">
           <name>format</name>
           <description>Date format to use.</description>
@@ -371,6 +384,39 @@ under the License.
       </fields>
     </class>
 
+    <class java.clone="deep">
+      <name>PublishDate</name>
+      <description>Modify display properties for date published.</description>
+      <version>1.0.0+</version>
+      <superClass>SiteDateFormat</superClass>
+      <fields>
+        <field xml.attribute="true">
+          <name>position</name>
+          <description>Where to place the published date ("left", "right", 
"navigation-top", "navigation-bottom", "bottom" or "none" to hide 
it).</description>
+          <version>1.0.0+</version>
+          <type>String</type>
+          <identifier>true</identifier>
+          <defaultValue>left</defaultValue>
+        </field>
+      </fields>
+    </class>
+
+    <class java.clone="deep">
+      <name>ModificationDate</name>
+      <description>Modify display properties for date modified. The date 
formatting options are taken over from "publishDate".</description>
+      <version>2.1.0+</version>
+      <fields>
+        <field xml.attribute="true">
+          <name>position</name>
+          <description>Where to place the last modified date ("left", "right", 
"navigation-top", "navigation-bottom", "bottom" or "none" to hide 
it).</description>
+          <version>2.1.0+</version>
+          <type>String</type>
+          <identifier>true</identifier>
+          <defaultValue>none</defaultValue>
+        </field>
+      </fields>
+    </class>
+    
     <class java.clone="deep">
       <name>Version</name>
       <description>Modify display properties for version 
published.</description>
diff --git a/doxia-site-renderer/pom.xml b/doxia-site-renderer/pom.xml
index 5cbbd3d..bebdfa9 100644
--- a/doxia-site-renderer/pom.xml
+++ b/doxia-site-renderer/pom.xml
@@ -36,6 +36,7 @@ under the License.
     <velocityEngineVersion>2.4.1</velocityEngineVersion>
     <velocityToolsVersion>3.1</velocityToolsVersion>
     <mermaidVersion>11.12.2</mermaidVersion>
+    <scmVersion>2.2.1</scmVersion>
   </properties>
   <dependencies>
     <dependency>
diff --git 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/ContextCustomizer.java
 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/ContextCustomizer.java
new file mode 100644
index 0000000..e81013f
--- /dev/null
+++ 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/ContextCustomizer.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.doxia.siterenderer;
+
+import org.apache.velocity.context.Context;
+
+@FunctionalInterface
+public interface ContextCustomizer {
+
+    /**
+     * Customize the Velocity context before rendering a document.
+     *
+     * @param context the Velocity context to customize.
+     * @param docRenderingContext the document rendering context for the 
document being rendered.
+     */
+    void customizeContext(
+            Context context, DocumentRenderingContext docRenderingContext, 
SiteRenderingContext siteRenderingContext);
+}
diff --git 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
index 82ed93e..ee937e5 100644
--- 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
+++ 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
@@ -143,6 +143,9 @@ public class DefaultSiteRenderer implements Renderer {
     @Inject
     private PlexusContainer plexus;
 
+    @Inject
+    private Map<String, ContextCustomizer> contextCustomizers;
+
     private static final String SKIN_TEMPLATE_LOCATION = 
"META-INF/maven/site.vm";
 
     private static final String TOOLS_LOCATION = 
"META-INF/maven/site-tools.xml";
@@ -267,8 +270,9 @@ public class DefaultSiteRenderer implements Renderer {
             docs.addAll(velocityFiles);
 
             for (String doc : docs) {
+
                 DocumentRenderingContext docRenderingContext = new 
DocumentRenderingContext(
-                        moduleBasedir, moduleRelativePath, doc, 
module.getParserId(), extension, editable);
+                        moduleBasedir, moduleRelativePath, doc, 
module.getParserId(), extension, editable, null);
 
                 // TODO: DOXIA-111: we need a general filter here that knows 
how to alter the context
                 if (endsWithIgnoreCase(doc, ".vm")) {
@@ -589,6 +593,18 @@ public class DefaultSiteRenderer implements Renderer {
             context.put("alignedFilePath", alignedFilePath);
             // TODO Deprecated -- will be removed!
             context.put("alignedFileName", alignedFilePath);
+
+            for (Map.Entry<String, ContextCustomizer> entry : 
contextCustomizers.entrySet()) {
+                try {
+                    LOGGER.debug("Applying Velocity context customizer '" + 
entry.getKey() + "'");
+                    entry.getValue().customizeContext(context, 
docRenderingContext, siteRenderingContext);
+                } catch (Exception e) {
+                    LOGGER.warn(
+                            "Velocity context customizer '" + entry.getKey()
+                                    + "' threw an exception and will be 
ignored",
+                            e);
+                }
+            }
         }
         context.put("site", siteRenderingContext.getSiteModel());
         // TODO Deprecated -- will be removed!
@@ -672,7 +688,11 @@ public class DefaultSiteRenderer implements Renderer {
         context.put("bodyContent", content.getBody());
 
         // document date (got from Doxia Sink date() API)
-        context.put("documentDate", content.getDate());
+        if (content.getDate() != null) {
+            context.put("documentDate", content.getDate());
+        } else {
+            context.put("documentDate", context.get("scmModifiedDate"));
+        }
 
         // document rendering context, to get eventual inputPath
         context.put("docRenderingContext", content.getRenderingContext());
diff --git 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
index 64db4c7..a4be2a6 100644
--- 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
+++ 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
@@ -22,6 +22,7 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -111,6 +112,8 @@ public class SiteRenderingContext {
 
     private ParserConfigurator parserConfigurator;
 
+    private final Map<String, Object> attributes = new HashMap<>();
+
     /**
      * If input documents should be validated before parsing.
      * By default no validation is performed.
@@ -454,4 +457,23 @@ public class SiteRenderingContext {
     public void setParserConfigurator(ParserConfigurator parserConfigurator) {
         this.parserConfigurator = parserConfigurator;
     }
+
+    /**
+     * Get the map of attributes that can be used by renderers and templates 
to customize the output.
+     * This is a free-form map that can be used to pass any additional 
information that may be needed during rendering, without having to extend the 
API.
+     *
+     * @return a map of attributes that can be used by renderers and templates 
to customize the output, the returned map is immutable, use {@link 
#addAttribute(String, Object)} to add attributes to the context
+     * @since 2.1.0
+     */
+    public Map<String, Object> getAttributes() {
+        return Collections.unmodifiableMap(attributes);
+    }
+
+    public Object putAttribute(String key, Object value) {
+        return this.attributes.put(key, value);
+    }
+
+    public Object removeAttribute(String key) {
+        return this.attributes.remove(key);
+    }
 }
diff --git 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java
 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java
index 332ce8a..37aa1c5 100644
--- 
a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java
+++ 
b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/sink/SiteRendererSink.java
@@ -62,6 +62,7 @@ public class SiteRendererSink extends Xhtml5Sink implements 
DocumentContent {
     private boolean containsMermaidDiagram = false;
 
     private boolean insideMermaidCodeElement = false;
+
     /**
      * Construct a new SiteRendererSink for a document.
      *
diff --git a/doxia-site-renderer/src/main/resources/site-renderer.properties 
b/doxia-site-renderer/src/main/resources/site-renderer.properties
index 793e4ea..8a1d8bc 100644
--- a/doxia-site-renderer/src/main/resources/site-renderer.properties
+++ b/doxia-site-renderer/src/main/resources/site-renderer.properties
@@ -16,6 +16,7 @@
 # under the License.
 
 template.lastpublished=Last Published
+template.lastmodified=Last Modified
 template.version=Version
 template.builtby=Built by
 template.externallinks=External Links
diff --git a/doxia-site-renderer/src/main/resources/site-renderer_de.properties 
b/doxia-site-renderer/src/main/resources/site-renderer_de.properties
index 1f8d8ec..cddf768 100644
--- a/doxia-site-renderer/src/main/resources/site-renderer_de.properties
+++ b/doxia-site-renderer/src/main/resources/site-renderer_de.properties
@@ -16,6 +16,7 @@
 # under the License.
 
 template.lastpublished=Zuletzt ver\u00F6ffentlicht
+template.lastmodified=Zuletzt ver\u00F6ndert
 template.builtby=Erstellt von
 template.externallinks=Externe Links
 template.edit=Bearbeiten
diff --git a/doxia-site-renderer/src/site/apt/index.apt.vm 
b/doxia-site-renderer/src/site/apt/index.apt.vm
index e97abd6..3f5ce13 100644
--- a/doxia-site-renderer/src/site/apt/index.apt.vm
+++ b/doxia-site-renderer/src/site/apt/index.apt.vm
@@ -75,6 +75,8 @@ Doxia Sitetools - Site Renderer
 
*---------------------------------+----------------------+-------------------------------+
 | <<<relativePath>>>              | <<<String>>>         | The path to the 
site root from the document being rendered. |
 
*---------------------------------+----------------------+-------------------------------+
+| <<<scmModifiedDate>>>           | <<<Date>>>           | The last modified 
date as retrieved through {{{https://maven.apache.org/scm/}Maven SCM}} from the 
underlying Doxia source. |
+*---------------------------------+----------------------+-------------------------------+
 | <<<site>>>                      | 
{{{../doxia-site-model/apidocs/org/apache/maven/doxia/site/SiteModel.html}<<<SiteModel>>>}}
 | This is a model that represents the data in your 
{{{../doxia-site-model/site.html}<<<site.xml>>>}}. |
 
*---------------------------------+----------------------+-------------------------------+
 | <<<supportedLocales>>>          | <<<List\<Locale\>>>> | The list of locales 
that the site will contain. |
@@ -108,7 +110,7 @@ Doxia Sitetools - Site Renderer
 
*------------------+---------------------------------------------------------------+-------------------------------+
 | <<<convert>>>    | {{{$generic${esc.hash}deprecated-tools}ConversionTool}}   
    | {{{$generic${esc.hash}deprecated-tools}<<Deprecated>>}}: use NumberTool 
for numbers formatting/parsing, DateTool for date/time formatting/parsing, or 
CollectionTool for toStrings(). For converting String values to richer object 
Types.
 
*------------------+---------------------------------------------------------------+-------------------------------+
-| <<<date>>>       | 
{{{$generic${esc.hash}ComparisonDateTool}ComparisonDateTool}} | For 
manipulating, formatting, and comparing dates.
+| <<<date>>>       | 
{{{$generic${esc.hash}ComparisonDateTool}ComparisonDateTool}} | For 
manipulating, formatting, and comparing dates. The formatting pattern and 
timezone are taken from the site descriptors element "publishDate" "format and 
"timezone" attributes, respectively.
 
*------------------+---------------------------------------------------------------+-------------------------------+
 | <<<display>>>    | {{{$generic${esc.hash}DisplayTool}DisplayTool}}           
    | For controlling display of references (e.g., truncating values, "pretty 
printing" lists, and displaying alternates when a reference is null).
 
*------------------+---------------------------------------------------------------+-------------------------------+
@@ -170,7 +172,7 @@ Doxia Sitetools - Site Renderer
 
*---------------------------------+----------------------+-------------------------------+
 | <<<bodyContent>>>               | <<<String>>>         | HTML body content 
of the Doxia generated output. |
 
*---------------------------------+----------------------+-------------------------------+
-| <<<documentDate>>>              | <<<String>>>         | The date specified 
in the source document: semantics has to be chosen by document writer (document 
creation date, or document last modification date, or ...), and format is not 
enforced. |
+| <<<documentDate>>>              | <<<String>>>         | The date specified 
in the source document: semantics has to be chosen by document writer (document 
creation date, or document last modification date, or ...), and format is not 
enforced. If not set is equal to <<<scmModifiedDate>>> |
 
*---------------------------------+----------------------+-------------------------------+
 | <<<headContent>>>               | <<<String>>>         | HTML head content 
of the Doxia generated output. |
 
*---------------------------------+----------------------+-------------------------------+
diff --git 
a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
 
b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
index d41ed81..7186981 100644
--- 
a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
+++ 
b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
@@ -334,8 +334,9 @@ public class DefaultSiteRendererTest {
         siteRenderingContext.setTemplateProperties(attributes);
 
         
siteRenderingContext.setTemplateName("org/apache/maven/doxia/siterenderer/velocity-toolmanager.vm");
+
         DocumentRenderingContext docRenderingContext =
-                new DocumentRenderingContext(new File(""), "document.html", 
"generator");
+                new DocumentRenderingContext(new File(""), null, 
"document.html", null, null, false, "generator");
         SiteRendererSink sink = new SiteRendererSink(docRenderingContext);
         siteRenderer.mergeDocumentIntoSite(writer, sink, siteRenderingContext);
 
@@ -371,7 +372,8 @@ public class DefaultSiteRendererTest {
         skin.setFile(skinFile);
         SiteRenderingContext siteRenderingContext =
                 siteRenderer.createContextForSkin(skin, attributes, new 
SiteModel(), "defaultitle", Locale.ROOT);
-        DocumentRenderingContext context = new DocumentRenderingContext(new 
File(""), "document.html", "generator");
+        DocumentRenderingContext context =
+                new DocumentRenderingContext(new File(""), null, 
"document.html", null, null, false, "generator");
         SiteRendererSink sink = new SiteRendererSink(context);
         siteRenderer.mergeDocumentIntoSite(writer, sink, siteRenderingContext);
         String renderResult = writer.toString();
@@ -525,6 +527,11 @@ public class DefaultSiteRendererTest {
         verifier.verify("target/output/mermaid.html");
     }
 
+    private void verifyLastModifiedPage() throws Exception {
+        LastModifiedVerifier verifier = new LastModifiedVerifier();
+        verifier.verify("target/output/lastmodified.html");
+    }
+
     /**
      * @throws Exception if something goes wrong.
      */
diff --git 
a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/LastModifiedVerifier.java
 
b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/LastModifiedVerifier.java
new file mode 100644
index 0000000..adbe1a8
--- /dev/null
+++ 
b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/LastModifiedVerifier.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.doxia.siterenderer;
+
+import org.htmlunit.html.DomNodeList;
+import org.htmlunit.html.HtmlElement;
+import org.htmlunit.html.HtmlMain;
+import org.htmlunit.html.HtmlPage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class LastModifiedVerifier extends AbstractVerifier {
+
+    @Override
+    public void verify(String file) throws Exception {
+        HtmlPage page = htmlPage(file);
+        assertNotNull(page);
+
+        HtmlElement element = page.getHtmlElementById("contentBox");
+        assertNotNull(element);
+        HtmlMain main = (HtmlMain) element;
+        assertNotNull(main);
+        DomNodeList<HtmlElement> paragraphs = main.getElementsByTagName("p");
+        assertEquals(1, paragraphs.size());
+        // formatting pattern "yyyy:MM:dd" given in site descriptor
+        assertEquals("Last modified: 2026:03:01", 
paragraphs.get(0).asNormalizedText());
+    }
+}
diff --git 
a/doxia-site-renderer/src/test/resources/site-last-modified/markdown/lastmodified.md.vm
 
b/doxia-site-renderer/src/test/resources/site-last-modified/markdown/lastmodified.md.vm
new file mode 100644
index 0000000..5f8fe8a
--- /dev/null
+++ 
b/doxia-site-renderer/src/test/resources/site-last-modified/markdown/lastmodified.md.vm
@@ -0,0 +1,21 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<!-- must be formatted according to site descriptor "publishDate format" -->
+Last modified: $date.format($scmModifiedDate)
\ No newline at end of file
diff --git a/doxia-site-renderer/src/test/resources/site-last-modified/site.xml 
b/doxia-site-renderer/src/test/resources/site-last-modified/site.xml
new file mode 100644
index 0000000..66fe6e9
--- /dev/null
+++ b/doxia-site-renderer/src/test/resources/site-last-modified/site.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<site xmlns="http://maven.apache.org/SITE/2.1.0";
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+  xsi:schemaLocation="http://maven.apache.org/SITE/2.1.0 
../../../../../doxia-site-model/target/generated-site/resources/xsd/site-2.1.0.xsd"
+  name="Plexus">
+  <bannerLeft name="Plexus" href="http://plexus.codehaus.org";>
+    <image src="http://plexus.codehaus.org/images/plexus-logo.png"; />
+  </bannerLeft>
+  <bannerRight href="http://www.codehaus.org";>
+    <image src="http://media.codehaus.org/images/unity-codehaus-logo.png"; />
+  </bannerRight>
+  <skin>
+    <groupId>org.apache.maven.skins</groupId>
+    <artifactId>maven-fluido-skin</artifactId>
+  </skin>
+  <publishDate position="none" format="yyyy:MM:dd" />
+  <modificationDate position="left" />
+</site>
diff --git a/doxia-site-scm-context/pom.xml b/doxia-site-scm-context/pom.xml
new file mode 100644
index 0000000..045feaa
--- /dev/null
+++ b/doxia-site-scm-context/pom.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.maven.doxia</groupId>
+    <artifactId>doxia-sitetools</artifactId>
+    <version>2.1.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>doxia-site-scm-context</artifactId>
+
+  <name>Doxia Sitetools :: SCM Context</name>
+  <description>Extends the document's Velocity context by SCM 
metadata.</description>
+
+  <properties>
+    <scmVersion>2.2.1</scmVersion>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-artifact</artifactId>
+      <version>${mavenVersion}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.inject</groupId>
+      <artifactId>javax.inject</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.maven.doxia</groupId>
+      <artifactId>doxia-site-renderer</artifactId>
+    </dependency>
+
+    <!-- scm dependencies -->
+    <dependency>
+      <groupId>org.apache.maven.scm</groupId>
+      <artifactId>maven-scm-api</artifactId>
+      <version>${scmVersion}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.scm</groupId>
+      <artifactId>maven-scm-providers-standard</artifactId>
+      <version>${scmVersion}</version>
+      <type>pom</type>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.scm</groupId>
+      <artifactId>maven-scm-manager-plexus</artifactId>
+      <version>${scmVersion}</version>
+      <scope>runtime</scope>
+    </dependency>
+
+    <!-- test -->
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-testing</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-api</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-engine</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.inject</groupId>
+      <artifactId>guice</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>4.11.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-junit-jupiter</artifactId>
+      <version>4.11.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <profiles>
+    <profile>
+      <id>run-its</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <executions>
+              <execution>
+                <goals>
+                  <goal>integration-test</goal>
+                  <goal>verify</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git 
a/doxia-site-scm-context/src/main/java/org/apache/maven/doxia/scm/siterenderer/ScmAttributesContextCustomizer.java
 
b/doxia-site-scm-context/src/main/java/org/apache/maven/doxia/scm/siterenderer/ScmAttributesContextCustomizer.java
new file mode 100644
index 0000000..312431a
--- /dev/null
+++ 
b/doxia-site-scm-context/src/main/java/org/apache/maven/doxia/scm/siterenderer/ScmAttributesContextCustomizer.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.doxia.scm.siterenderer;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Optional;
+
+import org.apache.maven.doxia.siterenderer.ContextCustomizer;
+import org.apache.maven.doxia.siterenderer.DocumentRenderingContext;
+import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.command.info.InfoItem;
+import org.apache.maven.scm.command.info.InfoScmResult;
+import org.apache.maven.scm.manager.ScmManager;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.velocity.context.Context;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link ContextCustomizer} that adds SCM attributes to the Velocity 
context for use in templates.
+ * It looks for an SCM repository in the directory of the site being rendered, 
and if found, retrieves SCM info for the file being rendered and adds it to the 
Velocity context.
+ *
+ * The SCM repository is lazily retrieved and cached in the 
SiteRenderingContext attributes for subsequent retrievals, so that it is only 
looked up once per site rendering.
+ *
+ * The SCM info is retrieved for each document being rendered, but only if an 
SCM repository was found for the site.
+ *
+ * The following attributes are added to the Velocity context:
+ * <ul>
+ * <li>{@value #ATTRIBUTE_NAME_SCM_MODIFIED_DATE}: the last modification date 
of the file being rendered according to SCM, as a {@link java.util.Date} (if 
available)</li>
+ * <li>{@value #ATTRIBUTE_NAME_SCM_MODIFIED_AUTHOR}: the author of the last 
modification of the file being rendered according to SCM, as a {@link String} 
(if available)</li>
+ * <ul>
+ *
+ * @since 2.1.0
+ */
+@Singleton
+@Named("scmAttributes")
+public class ScmAttributesContextCustomizer implements ContextCustomizer {
+
+    private static final String ATTRIBUTE_NAME_SCM_MODIFIED_AUTHOR = 
"scmModifiedAuthor";
+
+    private static final String ATTRIBUTE_NAME_SCM_MODIFIED_DATE = 
"scmModifiedDate";
+
+    private static final String KEY_SCM_REPOSITORY = 
"org.apache.maven.doxia.scm.siterenderer.scmRepository";
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ScmAttributesContextCustomizer.class);
+
+    private final ScmManager scmManager;
+
+    @Inject
+    ScmAttributesContextCustomizer(ScmManager scmManager) {
+        this.scmManager = scmManager;
+    }
+
+    /**
+     * Lazily retrieves the SCM repository for the site being rendered, 
caching it in the SiteRenderingContext attributes for subsequent retrievals.
+     * @param siteRenderingContext
+     * @return an Optional containing the SCM repository if found, or an empty 
Optional if not found or if an error occurs while trying to find it.
+     */
+    private Optional<ScmRepository> getScmRepository(SiteRenderingContext 
siteRenderingContext) {
+        if 
(siteRenderingContext.getAttributes().containsKey(KEY_SCM_REPOSITORY)) {
+            return Optional.ofNullable(
+                    (ScmRepository) 
siteRenderingContext.getAttributes().get(KEY_SCM_REPOSITORY));
+        } else {
+            Optional<ScmRepository> scmRepository =
+                    getScmRepository(scmManager, 
siteRenderingContext.getRootDirectory());
+            siteRenderingContext.putAttribute(KEY_SCM_REPOSITORY, 
scmRepository.orElse(null));
+            return scmRepository;
+        }
+    }
+
+    static Optional<ScmRepository> getScmRepository(ScmManager scmManager, 
File directory) {
+        Optional<ScmRepository> scmRepository = 
scmManager.makeProviderScmRepository(directory);
+        if (scmRepository.isPresent()) {
+            LOGGER.debug("Found SCM repository for directory \"{}\"", 
directory);
+        } else {
+            LOGGER.debug("No SCM repository found for directory {}", 
directory);
+            File parentDirectory = directory.getParentFile();
+            if (parentDirectory != null) {
+                return getScmRepository(scmManager, parentDirectory);
+            }
+        }
+        return scmRepository;
+    }
+
+    static InfoItem getScmInfo(ScmManager scmManager, ScmRepository 
scmRepository, File file) {
+        try {
+            ScmFileSet fileSet = new ScmFileSet(file.getParentFile(), 
Collections.singletonList(file));
+            InfoScmResult infos = scmManager
+                    .getProviderByRepository(scmRepository)
+                    .info(scmRepository.getProviderRepository(), fileSet, 
null);
+            if (infos != null && infos.isSuccess() && 
!infos.getInfoItems().isEmpty()) {
+                return infos.getInfoItems().get(0);
+            } else {
+                LOGGER.warn("Failed to get SCM info for file \"{}\": {}", 
file, infos);
+            }
+        } catch (ScmException e) {
+            LOGGER.warn("Failed to get SCM info for file \"{}\"", file, e);
+        }
+        return null;
+    }
+
+    @Override
+    public void customizeContext(
+            Context context, DocumentRenderingContext docRenderingContext, 
SiteRenderingContext siteRenderingContext) {
+        Optional<ScmRepository> scmRepository = 
getScmRepository(siteRenderingContext);
+
+        File inputFile = new File(docRenderingContext.getBasedir(), 
docRenderingContext.getInputPath());
+        final InfoItem scmInfo;
+        if (scmRepository.isPresent()) {
+            scmInfo = getScmInfo(scmManager, scmRepository.get(), inputFile);
+        } else {
+            scmInfo = null;
+        }
+
+        if (scmInfo != null) {
+            if (scmInfo.getLastChangedDateTime() != null) {
+                // Velocity can only deal with Date/Calendar
+                Date scmModifiedDate =
+                        
Date.from(scmInfo.getLastChangedDateTime().toInstant());
+                context.put(ATTRIBUTE_NAME_SCM_MODIFIED_DATE, scmModifiedDate);
+            } else {
+                LOGGER.warn(
+                        "SCM info for file \"{}\" does not contain last 
modification date, maybe not yet committed or not existing?",
+                        inputFile);
+            }
+            if (scmInfo.getLastChangedAuthor() != null) {
+                context.put(ATTRIBUTE_NAME_SCM_MODIFIED_AUTHOR, 
scmInfo.getLastChangedAuthor());
+            } else {
+                LOGGER.warn(
+                        "SCM info for file \"{}\" does not contain last 
author, maybe not yet committed or not existing?",
+                        inputFile);
+            }
+        } else {
+            LOGGER.debug("No SCM info available for file \"{}\"", inputFile);
+        }
+    }
+}
diff --git 
a/doxia-site-scm-context/src/test/java/org/apache/maven/doxia/scm/siterenderer/ScmAttributesContextCustomizerIT.java
 
b/doxia-site-scm-context/src/test/java/org/apache/maven/doxia/scm/siterenderer/ScmAttributesContextCustomizerIT.java
new file mode 100644
index 0000000..84f98ec
--- /dev/null
+++ 
b/doxia-site-scm-context/src/test/java/org/apache/maven/doxia/scm/siterenderer/ScmAttributesContextCustomizerIT.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.doxia.scm.siterenderer;
+
+import javax.inject.Inject;
+
+import java.io.File;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.Date;
+import java.util.Optional;
+
+import org.apache.maven.doxia.siterenderer.ContextCustomizer;
+import org.apache.maven.doxia.siterenderer.DocumentRenderingContext;
+import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
+import org.apache.maven.scm.command.info.InfoItem;
+import org.apache.maven.scm.manager.ScmManager;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.context.Context;
+import org.codehaus.plexus.testing.PlexusTest;
+import org.junit.jupiter.api.Test;
+
+import static org.codehaus.plexus.testing.PlexusExtension.getTestFile;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+@PlexusTest
+class ScmAttributesContextCustomizerIT {
+
+    @Inject
+    ScmManager scmManager;
+
+    @Test
+    void lastModifiedDate() throws Exception {
+        File siteDirectory = 
getTestFile("src/test/resources/site-last-modified");
+        File doxiaSource = new File(siteDirectory, 
"markdown/lastmodified.md.vm");
+        assertTrue(doxiaSource.exists(), "Test source file does not exist: " + 
doxiaSource.getAbsolutePath());
+        assumeTrue(isScmInfoAvailable(doxiaSource), "SCM info is not 
available, skipping test");
+
+        OffsetDateTime modifiedDate = OffsetDateTime.of(2024, 6, 1, 12, 0, 0, 
0, ZoneOffset.UTC);
+
+        ContextCustomizer contextCustomizer = new 
ScmAttributesContextCustomizer(scmManager);
+        Context context = new VelocityContext();
+        DocumentRenderingContext docContext =
+                new DocumentRenderingContext(doxiaSource.getParentFile(), 
doxiaSource.getName(), null);
+        SiteRenderingContext siteContext = new SiteRenderingContext();
+        siteContext.setRootDirectory(siteDirectory);
+        contextCustomizer.customizeContext(context, docContext, siteContext);
+        assertTrue(context.containsKey("scmModifiedDate"));
+        assertEquals(Date.from(modifiedDate.toInstant()), 
context.get("scmModifiedDate"));
+    }
+
+    boolean isScmInfoAvailable(File file) {
+        try {
+            Optional<ScmRepository> repo =
+                    
ScmAttributesContextCustomizer.getScmRepository(scmManager, 
file.getParentFile());
+            if (!repo.isPresent()) {
+                return false;
+            }
+            InfoItem infoItem = 
ScmAttributesContextCustomizer.getScmInfo(scmManager, repo.get(), file);
+            if (infoItem != null && infoItem.getLastChangedDateTime() != null) 
{
+                return true;
+            }
+            return false;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}
diff --git 
a/doxia-site-scm-context/src/test/java/org/apache/maven/doxia/scm/siterenderer/ScmAttributesContextCustomizerTest.java
 
b/doxia-site-scm-context/src/test/java/org/apache/maven/doxia/scm/siterenderer/ScmAttributesContextCustomizerTest.java
new file mode 100644
index 0000000..072fcfb
--- /dev/null
+++ 
b/doxia-site-scm-context/src/test/java/org/apache/maven/doxia/scm/siterenderer/ScmAttributesContextCustomizerTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.doxia.scm.siterenderer;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Optional;
+
+import org.apache.maven.doxia.siterenderer.ContextCustomizer;
+import org.apache.maven.doxia.siterenderer.DocumentRenderingContext;
+import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
+import org.apache.maven.scm.command.info.InfoItem;
+import org.apache.maven.scm.command.info.InfoScmResult;
+import org.apache.maven.scm.manager.ScmManager;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.apache.maven.scm.provider.ScmProviderRepository;
+import org.apache.maven.scm.repository.ScmRepository;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.context.Context;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+class ScmAttributesContextCustomizerTest {
+
+    @Mock
+    ScmManager scmManager;
+
+    @Mock
+    ScmRepository scmRepository;
+
+    @Mock
+    ScmProvider scmProvider;
+
+    @Mock
+    ScmProviderRepository scmProviderRepository;
+
+    private ContextCustomizer contextCustomizer;
+
+    private Context context;
+
+    private DocumentRenderingContext docContext;
+
+    private SiteRenderingContext siteContext;
+
+    private File siteDirectory;
+
+    @BeforeEach
+    void setup() throws IOException {
+        siteDirectory = new File(".").getCanonicalFile();
+        contextCustomizer = new ScmAttributesContextCustomizer(scmManager);
+        context = new VelocityContext();
+        docContext = new DocumentRenderingContext(siteDirectory, "test.md", 
null);
+        siteContext = new SiteRenderingContext();
+        siteContext.setRootDirectory(siteDirectory);
+    }
+
+    @Test
+    void lastModifiedDate() throws Exception {
+        
Mockito.when(scmManager.makeProviderScmRepository(siteDirectory)).thenReturn(Optional.of(scmRepository));
+        
Mockito.when(scmManager.getProviderByRepository(scmRepository)).thenReturn(scmProvider);
+        
Mockito.when(scmRepository.getProviderRepository()).thenReturn(scmProviderRepository);
+
+        InfoItem infoItem = new InfoItem();
+        OffsetDateTime modifiedDate = OffsetDateTime.of(2024, 6, 1, 12, 0, 0, 
0, ZoneOffset.UTC);
+        infoItem.setLastChangedDateTime(modifiedDate);
+
+        // equals not properly for any of the info(...) parameters, so use 
Mockito.any() for all parameters
+        Mockito.when(scmProvider.info(Mockito.any(), Mockito.any(), 
Mockito.any()))
+                .thenReturn(new InfoScmResult("", 
Collections.singletonList(infoItem)));
+
+        contextCustomizer.customizeContext(context, docContext, siteContext);
+        assertTrue(context.containsKey("scmModifiedDate"));
+        assertEquals(Date.from(modifiedDate.toInstant()), 
context.get("scmModifiedDate"));
+    }
+
+    @Test
+    void lastModifiedDateOutsideRepo() throws Exception {
+        
Mockito.when(scmManager.makeProviderScmRepository(siteDirectory)).thenReturn(Optional.empty());
+        contextCustomizer.customizeContext(context, docContext, siteContext);
+        assertFalse(context.containsKey("scmModifiedDate"));
+    }
+
+    @Test
+    void lastModifiedDateUnknown() throws Exception {
+        
Mockito.when(scmManager.makeProviderScmRepository(siteDirectory)).thenReturn(Optional.of(scmRepository));
+        
Mockito.when(scmManager.getProviderByRepository(scmRepository)).thenReturn(scmProvider);
+        
Mockito.when(scmRepository.getProviderRepository()).thenReturn(scmProviderRepository);
+
+        InfoItem infoItem = new InfoItem();
+
+        // equals not properly for any of the info(...) parameters, so use 
Mockito.any() for all parameters
+        Mockito.when(scmProvider.info(Mockito.any(), Mockito.any(), 
Mockito.any()))
+                .thenReturn(new InfoScmResult("", 
Collections.singletonList(infoItem)));
+        contextCustomizer.customizeContext(context, docContext, siteContext);
+        assertFalse(context.containsKey("scmModifiedDate"));
+    }
+}
diff --git 
a/doxia-site-scm-context/src/test/resources/site-last-modified/markdown/lastmodified.md.vm
 
b/doxia-site-scm-context/src/test/resources/site-last-modified/markdown/lastmodified.md.vm
new file mode 100644
index 0000000..5f8fe8a
--- /dev/null
+++ 
b/doxia-site-scm-context/src/test/resources/site-last-modified/markdown/lastmodified.md.vm
@@ -0,0 +1,21 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<!-- must be formatted according to site descriptor "publishDate format" -->
+Last modified: $date.format($scmModifiedDate)
\ No newline at end of file
diff --git 
a/doxia-site-scm-context/src/test/resources/site-last-modified/site.xml 
b/doxia-site-scm-context/src/test/resources/site-last-modified/site.xml
new file mode 100644
index 0000000..66fe6e9
--- /dev/null
+++ b/doxia-site-scm-context/src/test/resources/site-last-modified/site.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<site xmlns="http://maven.apache.org/SITE/2.1.0";
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+  xsi:schemaLocation="http://maven.apache.org/SITE/2.1.0 
../../../../../doxia-site-model/target/generated-site/resources/xsd/site-2.1.0.xsd"
+  name="Plexus">
+  <bannerLeft name="Plexus" href="http://plexus.codehaus.org";>
+    <image src="http://plexus.codehaus.org/images/plexus-logo.png"; />
+  </bannerLeft>
+  <bannerRight href="http://www.codehaus.org";>
+    <image src="http://media.codehaus.org/images/unity-codehaus-logo.png"; />
+  </bannerRight>
+  <skin>
+    <groupId>org.apache.maven.skins</groupId>
+    <artifactId>maven-fluido-skin</artifactId>
+  </skin>
+  <publishDate position="none" format="yyyy:MM:dd" />
+  <modificationDate position="left" />
+</site>
diff --git a/pom.xml b/pom.xml
index 9082e41..a445727 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,7 @@ under the License.
     <module>doxia-skin-model</module>
     <module>doxia-integration-tools</module>
     <module>doxia-site-renderer</module>
+    <module>doxia-site-scm-context</module>
   </modules>
 
   <scm>
@@ -125,6 +126,11 @@ under the License.
         <artifactId>doxia-skin-model</artifactId>
         <version>${project.version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.maven.doxia</groupId>
+        <artifactId>doxia-site-renderer</artifactId>
+        <version>${project.version}</version>
+      </dependency>
       <!-- Commons -->
       <dependency>
         <groupId>commons-io</groupId>

Reply via email to