diff --git a/versions-maven-plugin/pom.xml b/versions-maven-plugin/pom.xml
index c7a9dee648..872b61c92f 100644
--- a/versions-maven-plugin/pom.xml
+++ b/versions-maven-plugin/pom.xml
@@ -169,6 +169,14 @@
javax.inject
+
+
+
+ org.eclipse.jgit
+ org.eclipse.jgit
+ 5.13.3.202401111512-r
+
+
commons-codec
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/invoker.properties b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/invoker.properties
new file mode 100644
index 0000000000..852932e8a7
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/invoker.properties
@@ -0,0 +1,2 @@
+invoker.goals=${project.groupId}:${project.artifactId}:${project.version}:use-dynamic-version-from-scm
+invoker.buildResult = failure
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/pom.xml b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/pom.xml
new file mode 100644
index 0000000000..78bc48a827
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/pom.xml
@@ -0,0 +1,11 @@
+
+ 4.0.0
+ localhost
+ it-dynamic-versioning-scm-tag-0-commits-0-tags
+ ${revision}
+ pom
+ ranges-update-report
+ http://localhost/
+
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/selector.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/selector.groovy
new file mode 100644
index 0000000000..74f82d0070
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/selector.groovy
@@ -0,0 +1,10 @@
+try {
+ // smoke test if we have a needed tools
+ def gitVersion = "git --version".execute()
+ gitVersion.consumeProcessOutput(System.out, System.out)
+ gitVersion.waitFor()
+ return gitVersion.exitValue() == 0
+} catch (Exception e) {
+ // some error occurs - we skip a test
+ return false
+}
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/setup.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/setup.groovy
new file mode 100644
index 0000000000..b03e421918
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/setup.groovy
@@ -0,0 +1,11 @@
+void exec(String command) {
+ def proc = command.execute(null, basedir)
+ proc.consumeProcessOutput(System.out, System.out)
+ proc.waitFor()
+ assert proc.exitValue() == 0 : "Command '${command}' returned status: ${proc.exitValue()}"
+}
+
+def testFile = new File(basedir, 'test.txt')
+testFile << 'content'
+
+exec('git init')
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/verify.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/verify.groovy
new file mode 100644
index 0000000000..e342730544
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-0-commits-0-tags/verify.groovy
@@ -0,0 +1,12 @@
+// check that property was set
+
+def mavenLogFile = new File(basedir, 'build.log')
+assert mavenLogFile.exists() : "Maven log file does not exist"
+
+// Read the log file
+def logContent = mavenLogFile.text
+
+// Define a pattern to match the version output
+def versionPattern = /Caused by: org.apache.maven.plugin.MojoExecutionException: SCM repo has no head\/commits\./
+def matcher = (logContent =~ versionPattern)
+assert matcher.find() : "SCM repo should have no head/commits"
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/invoker.properties b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/invoker.properties
new file mode 100644
index 0000000000..b86b79ee0e
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/invoker.properties
@@ -0,0 +1 @@
+invoker.goals=${project.groupId}:${project.artifactId}:${project.version}:use-dynamic-version-from-scm
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/pom.xml b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/pom.xml
new file mode 100644
index 0000000000..4f6aef7e24
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/pom.xml
@@ -0,0 +1,11 @@
+
+ 4.0.0
+ localhost
+ it-dynamic-versioning-scm-tag-1-commit-0-tags
+ ${revision}
+ pom
+ ranges-update-report
+ http://localhost/
+
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/selector.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/selector.groovy
new file mode 100644
index 0000000000..74f82d0070
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/selector.groovy
@@ -0,0 +1,10 @@
+try {
+ // smoke test if we have a needed tools
+ def gitVersion = "git --version".execute()
+ gitVersion.consumeProcessOutput(System.out, System.out)
+ gitVersion.waitFor()
+ return gitVersion.exitValue() == 0
+} catch (Exception e) {
+ // some error occurs - we skip a test
+ return false
+}
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/setup.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/setup.groovy
new file mode 100644
index 0000000000..4053e7dd39
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/setup.groovy
@@ -0,0 +1,16 @@
+void exec(String command) {
+ def proc = command.execute(null, basedir)
+ proc.consumeProcessOutput(System.out, System.out)
+ proc.waitFor()
+ assert proc.exitValue() == 0 : "Command '${command}' returned status: ${proc.exitValue()}"
+}
+
+def testFile = new File(basedir, 'test.txt')
+testFile << 'content'
+
+exec('git init')
+exec('git config user.email "you@example.com"')
+exec('git config user.name "Your Name"')
+
+exec('git add test.txt')
+exec('git commit -m initial-commit')
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/verify.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/verify.groovy
new file mode 100644
index 0000000000..3c42ac51e6
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-1-commit-0-tags/verify.groovy
@@ -0,0 +1,19 @@
+// check that property was set
+
+def mavenLogFile = new File(basedir, 'build.log')
+assert mavenLogFile.exists() : "Maven log file does not exist"
+
+// Read the log file
+def logContent = mavenLogFile.text
+
+// Define a pattern to match the version output
+def versionPattern = /\[INFO\] Property 'revision' set to: (.+)/
+def matcher = (logContent =~ versionPattern)
+assert matcher.find() : "Version information not found in log file"
+
+// Extract the version from the matched group
+def actualVersion = matcher[0][1]
+
+def expectedVersion = '0.0.1-1-SNAPSHOT'
+
+assert actualVersion == expectedVersion : "Expected version '${expectedVersion}', but found '${actualVersion}'"
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/invoker.properties b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/invoker.properties
new file mode 100644
index 0000000000..03ed4f49ed
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/invoker.properties
@@ -0,0 +1 @@
+invoker.goals=-X ${project.groupId}:${project.artifactId}:${project.version}:use-dynamic-version-from-scm
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/pom.xml b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/pom.xml
new file mode 100644
index 0000000000..edb2c32957
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/pom.xml
@@ -0,0 +1,11 @@
+
+ 4.0.0
+ localhost
+ it-dynamic-versioning-scm-tag-4-commits-1-vtag
+ ${revision}
+ pom
+ ranges-update-report
+ http://localhost/
+
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/selector.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/selector.groovy
new file mode 100644
index 0000000000..74f82d0070
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/selector.groovy
@@ -0,0 +1,10 @@
+try {
+ // smoke test if we have a needed tools
+ def gitVersion = "git --version".execute()
+ gitVersion.consumeProcessOutput(System.out, System.out)
+ gitVersion.waitFor()
+ return gitVersion.exitValue() == 0
+} catch (Exception e) {
+ // some error occurs - we skip a test
+ return false
+}
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/setup.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/setup.groovy
new file mode 100644
index 0000000000..d529d2ab13
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/setup.groovy
@@ -0,0 +1,30 @@
+void exec(String command) {
+ def proc = command.execute(null, basedir)
+ proc.consumeProcessOutput(System.out, System.out)
+ proc.waitFor()
+ assert proc.exitValue() == 0 : "Command '${command}' returned status: ${proc.exitValue()}"
+}
+
+def testFile = new File(basedir, 'test.txt')
+testFile << 'content'
+
+exec('git init')
+exec('git config user.email "you@example.com"')
+exec('git config user.name "Your Name"')
+
+exec('git add test.txt')
+exec('git commit -m initial-commit')
+exec('git tag 1.1.1')
+exec('git tag')
+
+testFile << 'content2'
+exec('git add test.txt')
+exec('git commit -m commit_no2')
+
+testFile << 'content3'
+exec('git add test.txt')
+exec('git commit -m commit_no3')
+
+testFile << 'content4'
+exec('git add test.txt')
+exec('git commit -m commit_no4')
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/verify.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/verify.groovy
new file mode 100644
index 0000000000..3ddc76fc89
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-4-commits-1-vtag/verify.groovy
@@ -0,0 +1,19 @@
+// check that property was set
+
+def mavenLogFile = new File(basedir, 'build.log')
+assert mavenLogFile.exists() : "Maven log file does not exist"
+
+// Read the log file
+def logContent = mavenLogFile.text
+
+// Define a pattern to match the version output
+def versionPattern = /\[INFO\] Property 'revision' set to: (.+)/
+def matcher = (logContent =~ versionPattern)
+assert matcher.find() : "Version information not found in log file"
+
+// Extract the version from the matched group
+def actualVersion = matcher[0][1]
+
+def expectedVersion = '1.1.2-4-SNAPSHOT'
+
+assert actualVersion == expectedVersion : "Expected version '${expectedVersion}', but found '${actualVersion}'"
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/invoker.properties b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/invoker.properties
new file mode 100644
index 0000000000..b86b79ee0e
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/invoker.properties
@@ -0,0 +1 @@
+invoker.goals=${project.groupId}:${project.artifactId}:${project.version}:use-dynamic-version-from-scm
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/pom.xml b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/pom.xml
new file mode 100644
index 0000000000..f5de5973f3
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/pom.xml
@@ -0,0 +1,11 @@
+
+ 4.0.0
+ localhost
+ it-dynamic-versioning-scm-tag-latest-commit
+ ${revision}
+ pom
+ ranges-update-report
+ http://localhost/
+
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/selector.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/selector.groovy
new file mode 100644
index 0000000000..74f82d0070
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/selector.groovy
@@ -0,0 +1,10 @@
+try {
+ // smoke test if we have a needed tools
+ def gitVersion = "git --version".execute()
+ gitVersion.consumeProcessOutput(System.out, System.out)
+ gitVersion.waitFor()
+ return gitVersion.exitValue() == 0
+} catch (Exception e) {
+ // some error occurs - we skip a test
+ return false
+}
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/setup.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/setup.groovy
new file mode 100644
index 0000000000..a8873ec840
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/setup.groovy
@@ -0,0 +1,17 @@
+void exec(String command) {
+ def proc = command.execute(null, basedir)
+ proc.consumeProcessOutput(System.out, System.out)
+ proc.waitFor()
+ assert proc.exitValue() == 0 : "Command '${command}' returned status: ${proc.exitValue()}"
+}
+
+def testFile = new File(basedir, 'test.txt')
+testFile << 'content'
+
+exec('git init')
+exec('git config user.email "you@example.com"')
+exec('git config user.name "Your Name"')
+
+exec('git add test.txt')
+exec('git commit -m initial-commit')
+exec('git tag v1.1.1')
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/verify.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/verify.groovy
new file mode 100644
index 0000000000..86e3aa9330
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-latest-commit/verify.groovy
@@ -0,0 +1,19 @@
+// check that property was set
+
+def mavenLogFile = new File(basedir, 'build.log')
+assert mavenLogFile.exists() : "Maven log file does not exist"
+
+// Read the log file
+def logContent = mavenLogFile.text
+
+// Define a pattern to match the version output
+def versionPattern = /\[INFO\] Property 'revision' set to: (.+)/
+def matcher = (logContent =~ versionPattern)
+assert matcher.find() : "Version information not found in log file"
+
+// Extract the version from the matched group
+def actualVersion = matcher[0][1]
+
+def expectedVersion = '1.1.1'
+
+assert actualVersion == expectedVersion : "Expected version '${expectedVersion}', but found '${actualVersion}'"
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/invoker.properties b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/invoker.properties
new file mode 100644
index 0000000000..0218ef30cc
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/invoker.properties
@@ -0,0 +1 @@
+invoker.goals=${project.groupId}:${project.artifactId}:${project.version}:use-dynamic-version-from-scm -DappendSnapshot=false
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/pom.xml b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/pom.xml
new file mode 100644
index 0000000000..7acc0f9586
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/pom.xml
@@ -0,0 +1,11 @@
+
+ 4.0.0
+ localhost
+ it-dynamic-versioning-scm-tag-param-append-snapshot
+ ${revision}
+ pom
+ ranges-update-report
+ http://localhost/
+
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/selector.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/selector.groovy
new file mode 100644
index 0000000000..74f82d0070
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/selector.groovy
@@ -0,0 +1,10 @@
+try {
+ // smoke test if we have a needed tools
+ def gitVersion = "git --version".execute()
+ gitVersion.consumeProcessOutput(System.out, System.out)
+ gitVersion.waitFor()
+ return gitVersion.exitValue() == 0
+} catch (Exception e) {
+ // some error occurs - we skip a test
+ return false
+}
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/setup.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/setup.groovy
new file mode 100644
index 0000000000..4053e7dd39
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/setup.groovy
@@ -0,0 +1,16 @@
+void exec(String command) {
+ def proc = command.execute(null, basedir)
+ proc.consumeProcessOutput(System.out, System.out)
+ proc.waitFor()
+ assert proc.exitValue() == 0 : "Command '${command}' returned status: ${proc.exitValue()}"
+}
+
+def testFile = new File(basedir, 'test.txt')
+testFile << 'content'
+
+exec('git init')
+exec('git config user.email "you@example.com"')
+exec('git config user.name "Your Name"')
+
+exec('git add test.txt')
+exec('git commit -m initial-commit')
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/verify.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/verify.groovy
new file mode 100644
index 0000000000..522dee4138
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-append-snapshot/verify.groovy
@@ -0,0 +1,19 @@
+// check that property was set
+
+def mavenLogFile = new File(basedir, 'build.log')
+assert mavenLogFile.exists() : "Maven log file does not exist"
+
+// Read the log file
+def logContent = mavenLogFile.text
+
+// Define a pattern to match the version output
+def versionPattern = /\[INFO\] Property 'revision' set to: (.+)/
+def matcher = (logContent =~ versionPattern)
+assert matcher.find() : "Version information not found in log file"
+
+// Extract the version from the matched group
+def actualVersion = matcher[0][1]
+
+def expectedVersion = '0.0.1-1'
+
+assert actualVersion == expectedVersion : "Expected version '${expectedVersion}', but found '${actualVersion}'"
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/invoker.properties b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/invoker.properties
new file mode 100644
index 0000000000..dd8cc8fc1c
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/invoker.properties
@@ -0,0 +1 @@
+invoker.goals=${project.groupId}:${project.artifactId}:${project.version}:use-dynamic-version-from-scm -DuseVersion=9.9.9-9
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/pom.xml b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/pom.xml
new file mode 100644
index 0000000000..08131ac356
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/pom.xml
@@ -0,0 +1,11 @@
+
+ 4.0.0
+ localhost
+ it-dynamic-versioning-scm-tag-param-use-versions
+ ${revision}
+ pom
+ ranges-update-report
+ http://localhost/
+
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/selector.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/selector.groovy
new file mode 100644
index 0000000000..74f82d0070
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/selector.groovy
@@ -0,0 +1,10 @@
+try {
+ // smoke test if we have a needed tools
+ def gitVersion = "git --version".execute()
+ gitVersion.consumeProcessOutput(System.out, System.out)
+ gitVersion.waitFor()
+ return gitVersion.exitValue() == 0
+} catch (Exception e) {
+ // some error occurs - we skip a test
+ return false
+}
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/setup.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/setup.groovy
new file mode 100644
index 0000000000..4053e7dd39
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/setup.groovy
@@ -0,0 +1,16 @@
+void exec(String command) {
+ def proc = command.execute(null, basedir)
+ proc.consumeProcessOutput(System.out, System.out)
+ proc.waitFor()
+ assert proc.exitValue() == 0 : "Command '${command}' returned status: ${proc.exitValue()}"
+}
+
+def testFile = new File(basedir, 'test.txt')
+testFile << 'content'
+
+exec('git init')
+exec('git config user.email "you@example.com"')
+exec('git config user.name "Your Name"')
+
+exec('git add test.txt')
+exec('git commit -m initial-commit')
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/verify.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/verify.groovy
new file mode 100644
index 0000000000..4c2fe68a20
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-param-use-versions/verify.groovy
@@ -0,0 +1,19 @@
+// check that property was set
+
+def mavenLogFile = new File(basedir, 'build.log')
+assert mavenLogFile.exists() : "Maven log file does not exist"
+
+// Read the log file
+def logContent = mavenLogFile.text
+
+// Define a pattern to match the version output
+def versionPattern = /\[INFO\] Property 'revision' set to: (.+)/
+def matcher = (logContent =~ versionPattern)
+assert matcher.find() : "Version information not found in log file"
+
+// Extract the version from the matched group
+def actualVersion = matcher[0][1]
+
+def expectedVersion = '9.9.9-9'
+
+assert actualVersion == expectedVersion : "Expected version '${expectedVersion}', but found '${actualVersion}'"
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/invoker.properties b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/invoker.properties
new file mode 100644
index 0000000000..ca4ce2b72e
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/invoker.properties
@@ -0,0 +1 @@
+invoker.goals=${project.groupId}:${project.artifactId}:${project.version}:use-dynamic-version-from-scm -DpropertyName=version_from_scm
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/pom.xml b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/pom.xml
new file mode 100644
index 0000000000..593dcc5ae8
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/pom.xml
@@ -0,0 +1,11 @@
+
+ 4.0.0
+ localhost
+ it-dynamic-versioning-scm-tag-env-use-version
+ ${revision}
+ pom
+ ranges-update-report
+ http://localhost/
+
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/selector.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/selector.groovy
new file mode 100644
index 0000000000..74f82d0070
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/selector.groovy
@@ -0,0 +1,10 @@
+try {
+ // smoke test if we have a needed tools
+ def gitVersion = "git --version".execute()
+ gitVersion.consumeProcessOutput(System.out, System.out)
+ gitVersion.waitFor()
+ return gitVersion.exitValue() == 0
+} catch (Exception e) {
+ // some error occurs - we skip a test
+ return false
+}
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/setup.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/setup.groovy
new file mode 100644
index 0000000000..4053e7dd39
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/setup.groovy
@@ -0,0 +1,16 @@
+void exec(String command) {
+ def proc = command.execute(null, basedir)
+ proc.consumeProcessOutput(System.out, System.out)
+ proc.waitFor()
+ assert proc.exitValue() == 0 : "Command '${command}' returned status: ${proc.exitValue()}"
+}
+
+def testFile = new File(basedir, 'test.txt')
+testFile << 'content'
+
+exec('git init')
+exec('git config user.email "you@example.com"')
+exec('git config user.name "Your Name"')
+
+exec('git add test.txt')
+exec('git commit -m initial-commit')
diff --git a/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/verify.groovy b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/verify.groovy
new file mode 100644
index 0000000000..603724bd4a
--- /dev/null
+++ b/versions-maven-plugin/src/it/it-dynamic-versioning-scm-tag-property-name/verify.groovy
@@ -0,0 +1,19 @@
+// check that property was set
+
+def mavenLogFile = new File(basedir, 'build.log')
+assert mavenLogFile.exists() : "Maven log file does not exist"
+
+// Read the log file
+def logContent = mavenLogFile.text
+
+// Define a pattern to match the version output
+def versionPattern = /\[INFO\] Property 'version_from_scm' set to: (.+)/
+def matcher = (logContent =~ versionPattern)
+assert matcher.find() : "Version information not found in log file"
+
+// Extract the version from the matched group
+def actualVersion = matcher[0][1]
+
+def expectedVersion = '0.0.1-1-SNAPSHOT'
+
+assert actualVersion == expectedVersion : "Expected version '${expectedVersion}', but found '${actualVersion}'"
diff --git a/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/DynamicVersioningSCMPlugin.java b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/DynamicVersioningSCMPlugin.java
new file mode 100644
index 0000000000..bde84f1de7
--- /dev/null
+++ b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/DynamicVersioningSCMPlugin.java
@@ -0,0 +1,240 @@
+package org.codehaus.mojo.versions;
+
+/*
+ * Copyright MojoHaus and Contributors
+ * Licensed 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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.maven.artifact.versioning.ComparableVersion;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+
+/**
+ * Maven plugin that uses SCM/VCS to enable dynamic versioning based on your
+ * version control system. Goal will set the version in a property.
+ *
+ * @author Jimisola Laursen
+ * @since 2.17.0
+ */
+@Mojo(name = "use-dynamic-version-from-scm", defaultPhase = LifecyclePhase.INITIALIZE)
+public class DynamicVersioningSCMPlugin extends AbstractMojo {
+
+ /**
+ * The Maven Project Object
+ *
+ * @since 2.17.0
+ */
+ @Parameter(defaultValue = "${project}", readonly = true, required = true)
+ private MavenProject project;
+
+ /**
+ * The name of the property that will contain the resolved version.
+ *
+ * @since 2.17.0
+ */
+ @Parameter(property = "propertyName", defaultValue = "revision")
+ protected String propertyName;
+
+ /**
+ * Whether the SNAPSHOT qualifier shall be apppended or not.
+ *
+ * @since 2.17.0
+ */
+ @Parameter(property = "appendSnapshot", defaultValue = "true")
+ protected boolean appendSnapshot;
+
+ /**
+ * Use this version instead of resolving from SCM tag information.
+ *
+ * @since 2.17.0
+ */
+ @Parameter(property = "useVersion")
+ protected String useVersion;
+
+ /**
+ * The default version used when SCM repository has no commit or no version
+ * tag.
+ *
+ * @since 2.17.0
+ */
+ @Parameter(property = "defaultVersion", defaultValue = "0.0.1")
+ protected String defaultVersion;
+
+ // standard semantic versioning with an optional 'v' prefix
+ protected static final Pattern TAG_VERSION_PATTERN = Pattern.compile("refs/tags/(?:v)?((\\d+\\.\\d+\\.\\d+)(.*))");
+
+ public void execute() throws MojoExecutionException {
+ // limit JGits excessive logging
+ Logger.getLogger("org.eclipse.jgit").setLevel(Level.INFO);
+
+ VersionInformation vi;
+
+ Optional mayBeVersion = Optional.ofNullable(useVersion);
+
+ if (mayBeVersion.isPresent()) {
+ vi = new VersionInformation(mayBeVersion.get());
+ } else {
+ vi = getVersionFromSCM();
+ }
+
+ project.getProperties().setProperty(propertyName, vi.toString());
+ getLog().info("Property '" + propertyName + "' set to: "
+ + project.getProperties().getProperty(propertyName));
+ }
+
+ /**
+ * Returns the resolved version based on SCM tag information for use with Maven
+ * CI.
+ *
+ * @throws org.apache.maven.plugin.MojoExecutionException Something wrong with
+ * the
+ * plugin itself
+ */
+ protected VersionInformation getVersionFromSCM() throws MojoExecutionException {
+ // check for repository
+ try (Repository repository = new FileRepositoryBuilder()
+ .setGitDir(new File(".git"))
+ .readEnvironment() // scan environment GIT_* variables
+ .findGitDir() // scan up the file system tree
+ .build(); ) {
+
+ if (repository.getDirectory() == null) {
+ throw new MojoExecutionException("Directory is not an SCM repository.");
+ }
+
+ // check for latest commit
+ RevCommit latestCommit = getLatestCommit(repository);
+
+ return getVersionFromCommit(repository, latestCommit);
+ } catch (IOException e) {
+ throw new MojoExecutionException("Error reading Git information.", e);
+ }
+ }
+
+ protected RevCommit getLatestCommit(Repository repository) throws MojoExecutionException {
+ try (RevWalk revWalk = new RevWalk(repository)) {
+ ObjectId head = repository.resolve("HEAD");
+
+ if (head == null) {
+ throw new MojoExecutionException("SCM repo has no head/commits.");
+ }
+
+ return revWalk.parseCommit(head);
+ } catch (IOException e) {
+ throw new MojoExecutionException("SCM repo most likely has no commits.", e);
+ }
+ }
+
+ protected VersionInformation getVersionFromCommit(Repository repository, RevCommit latestCommit)
+ throws MojoExecutionException {
+
+ try (Git git = Git.wrap(repository)) {
+
+ List versionTags = getVersionedTagsForCommit(git, latestCommit);
+
+ Optional ovi = findHighestVersion(versionTags);
+
+ // latest commit has version tag(s), we use the highest one
+ if (ovi.isPresent()) {
+ return ovi.get();
+ }
+
+ Iterable commits = git.log().call();
+ int count = 0;
+ for (RevCommit commit : commits) {
+ count++;
+
+ versionTags = getVersionedTagsForCommit(git, commit);
+
+ ovi = findHighestVersion(versionTags);
+
+ if (ovi.isPresent()) {
+ VersionInformation vi = ovi.get();
+
+ vi.setPatch(vi.getPatch() + 1);
+ vi.setBuildNumber(count);
+
+ return addSnapshotQualifier(vi);
+ }
+ }
+
+ // no version tags in repository
+ return addSnapshotQualifier(new VersionInformation(defaultVersion + "-" + count));
+
+ } catch (GitAPIException e) {
+ throw new MojoExecutionException("Error reading Git information.", e);
+ }
+ }
+
+ protected Optional findHighestVersion(List versionTags) {
+ Optional highestVersionString = versionTags.stream().max(this.new VersionComparator());
+
+ return highestVersionString.map(VersionInformation::new);
+ }
+
+ protected List getVersionedTagsForCommit(Git git, RevCommit commit) throws GitAPIException {
+ // get tags directly associated with the commit
+ return git.tagList().call().stream()
+ .filter(tag -> tag.getObjectId().equals(commit.getId()))
+ .map(Ref::getName)
+ .filter(tagName -> {
+ Matcher matcher = TAG_VERSION_PATTERN.matcher(tagName);
+ return matcher.matches() && matcher.groupCount() > 0;
+ })
+ .map(tagName -> {
+ Matcher matcher = TAG_VERSION_PATTERN.matcher(tagName);
+ matcher.matches();
+ return matcher.group(1);
+ })
+ .collect(Collectors.toList());
+ }
+
+ protected VersionInformation addSnapshotQualifier(VersionInformation vi) {
+ if (appendSnapshot) {
+ vi.setQualifier("SNAPSHOT");
+ }
+
+ return vi;
+ }
+
+ protected class VersionComparator implements Comparator {
+
+ @Override
+ public int compare(String version1, String version2) {
+ return new ComparableVersion(version1).compareTo(new ComparableVersion(version2));
+ }
+ }
+}
diff --git a/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/VersionInformation.java b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/VersionInformation.java
new file mode 100644
index 0000000000..1cfbe492d5
--- /dev/null
+++ b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/VersionInformation.java
@@ -0,0 +1,176 @@
+package org.codehaus.mojo.versions;
+
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, 2017 Karl Heinz Marbaise
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class will parse the version based on the given pattern in
+ * {@code org.codehaus.mojo.buildhelper.ParseVersionMojo}.
+ *
+ * @author Karl Heinz Marbaise
+ * khmarbaise@apache.org
+ *
+ */
+public class VersionInformation {
+
+ private static final String MAJOR_MINOR_PATCH_PATTERN = "^((\\d+)(\\.(\\d+)(\\.(\\d+))?)?)";
+
+ private static final Pattern MAJOR_MINOR_PATCH = Pattern.compile(MAJOR_MINOR_PATCH_PATTERN);
+
+ private static final Pattern DIGITS = Pattern.compile(MAJOR_MINOR_PATCH_PATTERN + "(.*)$");
+
+ private static final Pattern BUILD_NUMBER = Pattern.compile("(((\\-)(\\d+)(.*))?)|(\\.(.*))|(\\-(.*))|(.*)$");
+
+ private int major;
+
+ private int minor;
+
+ private int patch;
+
+ private long buildNumber;
+
+ private String qualifier;
+
+ private void parseBuildNumber(String buildNumberPart) {
+ Matcher matcher = BUILD_NUMBER.matcher(buildNumberPart);
+ if (matcher.matches()) {
+ String buildNumber = matcher.group(4);
+ String qualifier = matcher.group(5);
+
+ if (buildNumber != null) {
+ setBuildNumber(Long.parseLong(buildNumber));
+ }
+
+ if (matcher.group(7) != null) {
+ qualifier = matcher.group(7);
+ }
+ // Starting with "-"
+ if (matcher.group(9) != null) {
+ qualifier = matcher.group(9);
+ }
+ if (qualifier != null) {
+ if (qualifier.trim().length() == 0) {
+ setQualifier(null);
+ } else {
+ setQualifier(qualifier);
+ }
+ } else {
+ setQualifier(null);
+ }
+ }
+ }
+
+ private void parseMajorMinorPatchVersion(String version) {
+ Matcher matcher = MAJOR_MINOR_PATCH.matcher(version);
+ if (matcher.matches()) {
+ String majorString = matcher.group(2);
+ String minorString = matcher.group(4);
+ String patchString = matcher.group(6);
+
+ if (majorString != null) {
+ setMajor(Integer.parseInt(majorString));
+ }
+ if (minorString != null) {
+ setMinor(Integer.parseInt(minorString));
+ }
+ if (patchString != null) {
+ setPatch(Integer.parseInt(patchString));
+ }
+ }
+ }
+
+ public VersionInformation(String version) {
+ Matcher matcherDigits = DIGITS.matcher(version);
+ if (matcherDigits.matches()) {
+ parseMajorMinorPatchVersion(matcherDigits.group(1));
+ parseBuildNumber(matcherDigits.group(7));
+ } else {
+ setQualifier(version);
+ }
+ }
+
+ public int getMajor() {
+ return major;
+ }
+
+ public void setMajor(int major) {
+ this.major = major;
+ }
+
+ public int getMinor() {
+ return minor;
+ }
+
+ public void setMinor(int minor) {
+ this.minor = minor;
+ }
+
+ public int getPatch() {
+ return patch;
+ }
+
+ public void setPatch(int patch) {
+ this.patch = patch;
+ }
+
+ public long getBuildNumber() {
+ return buildNumber;
+ }
+
+ public void setBuildNumber(long buildNumber) {
+ this.buildNumber = buildNumber;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public void setQualifier(String qualifier) {
+ this.qualifier = qualifier;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(this.getMajor());
+ sb.append("." + this.getMinor());
+ sb.append("." + this.getPatch());
+
+ if (this.getQualifier() != null || this.getBuildNumber() != 0) {
+
+ if (this.getBuildNumber() != 0) {
+ sb.append("-");
+ sb.append(this.getBuildNumber());
+ }
+ if (this.getQualifier() != null) {
+ sb.append("-");
+ sb.append(this.getQualifier());
+ }
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/versions-maven-plugin/src/site/markdown/examples/use-dynamic-version-from-scm.md b/versions-maven-plugin/src/site/markdown/examples/use-dynamic-version-from-scm.md
new file mode 100644
index 0000000000..895db7ebeb
--- /dev/null
+++ b/versions-maven-plugin/src/site/markdown/examples/use-dynamic-version-from-scm.md
@@ -0,0 +1,32 @@
+title: Using use-dynamic-version-from-scm goal
+author: Jimisola Laursen
+date: 2024-06-22
+
+
+
+The `use-dynamic-version-from-scm` goal will use scm/vcs (currently, only git) tags to determine the version for the current build and set that in a property (default: `revision`). It is intended to be used with [Maven CI Friendly Versions](https://maven.apache.org/maven-ci-friendly.html). The goal follows [Versions Maven Plugin - Version number rules](https://www.mojohaus.org/versions/versions-maven-plugin/version-rules.html).
+
+* The version tag can be with or without "v" prefix, i.e. "v1.2.3" or "1.2.3".
+* The `-SNAPSHOT` qualifier suffix is optional (appended per default).
+* If the parameter `useVersion` is set then that version will be used irrespective of commits/tags (is typically used for testing).
+* The goal will determine the version for the current build as follows:
+ * latest commit has valid version tag: use the highest version tag for that commit (e.g. 1.2.3 -> 1.2.3)
+ * latest version has _no_ valid version tag: use the highest version tag for the most recent non-latest commit, increase incremental (patch) version with 1 and use the number of commits to that commit (commit count) as build number and `-SNAPSHOT qualifer appended (e.g. 1.2.3 -> 1.2.4-12-SNAPSHOT)
+ * no commit with valid version tag: use 0.0.1 (configurable) and use the number of commits to that commit (commit count) as build number and `-SNAPSHOT qualifer appended (e.g. 1.2.3 -> 1.2.4-12-SNAPSHOT) (e.g. 0.0.1-10-SNAPSHOT)
+ * repository has no commits: build fails with exception `Caused by: org.apache.maven.plugin.MojoExecutionException: SCM repo has no head/commits.`
+
diff --git a/versions-maven-plugin/src/site/markdown/index.md b/versions-maven-plugin/src/site/markdown/index.md
index a0f773e6b4..48c386d5ed 100644
--- a/versions-maven-plugin/src/site/markdown/index.md
+++ b/versions-maven-plugin/src/site/markdown/index.md
@@ -73,6 +73,7 @@ have been a newer version and replaces them with the latest version.
built-in "Poor Man's SCM".
* [versions:revert](./revert-mojo.html) restores the `pom.xml` files from the `pom.xml.versionsBackup` files.
Forms one half of the built-in "Poor Man's SCM".
+* [versions:use-dynamic-version-from-scm](./use-dynamic-version-from-scm-mojo.html) uses SCM/VCS tags to enable dynamic versioning and sets the version in a property.
## Reporting goals overview
@@ -135,3 +136,4 @@ you can take a look into the following examples:
* [Replacing -SNAPSHOT versions with their corresponding releases](./examples/use-releases.html)
* [Changing the project version](./examples/set.html)
* [Recording version changes](./examples/recording-changes.html)
+* [Using use-dynamic-version-from-scm goal](./examples/use-dynamic-version-from-scm.html)