├── .gitignore ├── Jenkinsfile ├── LICENSE.txt ├── README.adoc ├── pom.xml └── src ├── it ├── release │ ├── pom.xml │ └── src │ │ ├── filtered │ │ └── resources │ │ │ └── version.filtered.txt │ │ ├── invoker.properties │ │ └── test │ │ └── java │ │ └── it │ │ └── VerificationTest.java ├── settings.xml └── smokes │ ├── pom.xml │ └── src │ ├── filtered │ └── resources │ │ ├── timestamp.filtered.txt │ │ └── version.filtered.txt │ ├── invoker.properties │ └── test │ └── java │ └── it │ └── VerificationTest.java └── main └── java └── com └── github └── stephenc └── continuous └── gittimestamp ├── AbstractGitOpsMojo.java ├── GitCommandLineLogger.java ├── ReleaseMojo.java └── TimestampMojo.java /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | .settings 4 | *.iml 5 | *.ipr 6 | *.iws 7 | .idea/ 8 | target/ 9 | .repository/ 10 | release.properties 11 | pom.xml.releaseBackup 12 | .gnupg/ 13 | TAG_NAME.txt 14 | VERSION.txt 15 | 16 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | options { 4 | buildDiscarder logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '1', daysToKeepStr: '', numToKeepStr: '10') 5 | disableConcurrentBuilds() 6 | } 7 | stages { 8 | stage('Build') { 9 | when { 10 | not { branch 'master' } 11 | } 12 | steps { 13 | withMaven(maven:'maven-3', jdk:'java-8', mavenLocalRepo: '.repository') { 14 | sh 'mvn verify' 15 | } 16 | } 17 | } 18 | stage('Release') { 19 | when { 20 | branch 'master' 21 | } 22 | environment { 23 | // We need to put the .gnupg homedir somewhere, the workspace is too long a path name 24 | // for the sockets, so we instead use a subdirectory of the user home (typically /home/jenkins). 25 | // By using the executor number as part of that name, we ensure nobody else will concurrently 26 | // use this directory 27 | GNUPGHOME = "${HOME}/.e${EXECUTOR_NUMBER}/.gnupg" 28 | } 29 | steps { 30 | withCredentials([ 31 | file(credentialsId: '.gnupg', variable: 'GNUPGHOME_ZIP'), 32 | string(credentialsId: 'gpg.passphrase', variable: 'GPG_PASSPHRASE') 33 | ]) { 34 | // Install the .gnupg directory 35 | sh ''' 36 | gpgconf --kill gpg-agent 37 | rm -rf "$(dirname "${GNUPGHOME}")" 38 | mkdir -p "${GNUPGHOME}" 39 | chmod 700 "${GNUPGHOME}" 40 | unzip "${GNUPGHOME_ZIP}" -d "${GNUPGHOME}" 41 | gpgconf --launch gpg-agent 42 | ''' 43 | 44 | // Create and stage release 45 | withMaven(maven:'maven-3', jdk:'java-8', mavenLocalRepo: '.repository', mavenSettingsConfig: 'oss-sonatype-publish') { 46 | sh 'mvn release:clean git-timestamp:setup-release release:prepare release:perform' 47 | } 48 | } 49 | } 50 | post { 51 | always { 52 | // Uninstall .gnupg directory 53 | sh 'gpgconf --kill gpg-agent || true ; rm -rf "$(dirname "${GNUPGHOME}")"' 54 | } 55 | success { 56 | // Publish the tag 57 | sshagent(['github-ssh']) { 58 | // using the full url so that we do not care if https checkout used in Jenkins 59 | sh 'git push git@github.com:stephenc/git-timestamp-maven-plugin.git $(cat TAG_NAME.txt)' 60 | } 61 | // Release the artifacts 62 | withMaven(mavenLocalRepo: '.repository', mavenSettingsConfig: 'oss-sonatype-publish', maven:'maven-3', jdk:'java-8') { 63 | sh 'mvn -f target/checkout/pom.xml nexus-staging:release' 64 | } 65 | 66 | // Set the display name to the version so it is easier to see in the UI 67 | script { currentBuild.displayName = readFile('VERSION.txt').trim() } 68 | } 69 | failure { 70 | // Remove the local tag as there is no matching remote tag 71 | sh 'test -f TAG_NAME.txt && git tag -d $(cat TAG_NAME.txt) && rm -f TAG_NAME.txt || true' 72 | 73 | // Drop staging repo 74 | withMaven(mavenLocalRepo: '.repository', mavenSettingsConfig: 'oss-sonatype-publish', maven:'maven-3', jdk:'java-8') { 75 | sh 'mvn -f target/checkout/pom.xml nexus-staging:drop || true' 76 | } 77 | 78 | } 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | == Git Timestamp Maven Plugin 2 | 3 | Experimental plugin to assist with applying continuous delivery to Maven based projects. 4 | 5 | If you are following the type of release pattern described in https://www.cloudbees.com/blog/new-way-do-continuous-delivery-maven-and-jenkins-pipeline[this blog post] you may need a way to assign "version numbers" for the developer local builds. 6 | 7 | This plugin will infer a timestamp from the state of the git repository. 8 | The timestamp will have two parts: 9 | 10 | * The time of the most recently modified tracked file in the workspace. 11 | * The number of commits on the current branch. 12 | 13 | The timestamp can then be injected into a system property (for use in filtering resources) 14 | and/or can be writted to a file. 15 | 16 | Additionally, the timestamp can be substituted into the project version. 17 | By default release versions will be left alone, but `-SNAPSHOT` versions will have the `-SNAPSHOT` replaced by the timestamp. 18 | The modified version can then be injected into a system property (for use in filtering resources) and/or can be writted to a file. 19 | 20 | For example, the configuration: 21 | 22 | [source,xml] 23 | ---- 24 | 25 | ... 26 | 1.x-SNAPSHOT 27 | ... 28 | 29 | ... 30 | 31 | ... 32 | 33 | ... 34 | maven-release-plugin 35 | ... 36 | 37 | true 38 | false 39 | 1.${env.BUILD_NUMBER} 40 | 1.x-SNAPSHOT 41 | 42 | 43 | ... 44 | 45 | com.github.stephenc.continuous 46 | git-timestamp-maven-plugin 47 | ... 48 | 49 | 50 | 51 | timestamp 52 | 53 | 54 | 55 | 56 | ${project.build.outputDirectory}/version.txt 57 | 58 | 59 | ... 60 | 61 | ... 62 | 63 | ... 64 | 65 | ---- 66 | 67 | Will populate the `version.txt` file with a version like `1.x-20180220.191333-54` for developer builds. 68 | 69 | When the release is done on the CI server (which defines the environment variable `BUILD_NUMBER` then the `version.txt` file will be populated with a version like `1.67` 70 | 71 | This way, developer build will have a version number in the `version.txt` that is reflective of their local changes and will increase as they make newer modifications. 72 | 73 | Additionally, release versions will always be seen as newer than developer builds (because `1.x` is less than `1.1` using Maven's version number comparison rules). 74 | 75 | == Release assistance 76 | 77 | This plugin can also assist the Apache Maven Release plugin. 78 | For example: 79 | 80 | For example, the configuration: 81 | 82 | [source,xml] 83 | ---- 84 | 85 | ... 86 | 1.x-SNAPSHOT 87 | ... 88 | 89 | ... 90 | 91 | ... 92 | 93 | ... 94 | maven-release-plugin 95 | ... 96 | 97 | true 98 | false 99 | 100 | 101 | ... 102 | 103 | com.github.stephenc.continuous 104 | git-timestamp-maven-plugin 105 | ... 106 | 107 | x-SNAPSHOT 108 | 109 | 110 | ... 111 | 112 | ... 113 | 114 | ... 115 | 116 | ---- 117 | 118 | Will enable automatic version detection based on the current remote tags and the number of commits when Maven is invoked like so: 119 | 120 | [source,bash] 121 | ---- 122 | mvn git-timestamp:setup-release release:prepare release:perform 123 | ---- 124 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 4.0.0 19 | 20 | 21 | org.sonatype.oss 22 | oss-parent 23 | 9 24 | 25 | 26 | com.github.stephenc.continuous 27 | git-timestamp-maven-plugin 28 | 1.x-SNAPSHOT 29 | maven-plugin 30 | 31 | Git Timestamp Maven Plugin 32 | A plugin that computes a consistent timestamp based on your git sources. 33 | http://stephenc.github.com/git-timestamp-maven-plugin 34 | 2018 35 | 36 | 37 | The Apache Software License, Version 2.0 38 | http://www.apache.org/licenses/LICENSE-2.0.txt 39 | repo 40 | 41 | 42 | 43 | 3.0 44 | 45 | 46 | 47 | 48 | stephenc 49 | Stephen Connolly 50 | 51 | Developer 52 | 53 | 54 | 55 | 56 | 57 | scm:git:https://github.com/stephenc/git-timestamp-maven-plugin.git 58 | scm:git:git@github.com:stephenc/git-timestamp-maven-plugin.git 59 | http://github.com/stephenc/git-timestamp-maven-plugin/tree/master/ 60 | HEAD 61 | 62 | 63 | github 64 | http://github.com/stephenc/git-timestamp-maven-plugin/issues 65 | 66 | 67 | 68 | UTF-8 69 | UTF-8 70 | UTF-8 71 | 3.0.5 72 | 1.9.5 73 | 74 | 75 | 76 | 77 | org.apache.maven 78 | maven-model 79 | ${maven.version} 80 | 81 | 82 | org.apache.maven 83 | maven-core 84 | ${maven.version} 85 | 86 | 87 | org.apache.maven 88 | maven-plugin-api 89 | ${maven.version} 90 | 91 | 92 | org.codehaus.plexus 93 | plexus-utils 94 | 2.0.5 95 | 96 | 97 | org.codehaus.plexus 98 | plexus-container-default 99 | 1.0-alpha-9 100 | 101 | 102 | 103 | org.apache.maven.plugin-tools 104 | maven-plugin-annotations 105 | 3.0 106 | compile 107 | 108 | 109 | org.apache.maven.scm 110 | maven-scm-api 111 | ${maven.scm.version} 112 | 113 | 114 | org.apache.maven.scm 115 | maven-scm-manager-plexus 116 | ${maven.scm.version} 117 | 118 | 119 | org.apache.maven.scm 120 | maven-scm-provider-bazaar 121 | ${maven.scm.version} 122 | 123 | 124 | org.apache.maven.scm 125 | maven-scm-provider-svnexe 126 | ${maven.scm.version} 127 | 128 | 129 | org.apache.maven.scm 130 | maven-scm-provider-gitexe 131 | ${maven.scm.version} 132 | 133 | 134 | org.apache.maven.scm 135 | maven-scm-provider-svn-commons 136 | ${maven.scm.version} 137 | 138 | 139 | org.apache.maven.scm 140 | maven-scm-provider-cvsexe 141 | ${maven.scm.version} 142 | 143 | 144 | org.apache.maven.scm 145 | maven-scm-provider-starteam 146 | ${maven.scm.version} 147 | 148 | 149 | org.apache.maven.scm 150 | maven-scm-provider-clearcase 151 | ${maven.scm.version} 152 | 153 | 154 | org.apache.maven.scm 155 | maven-scm-provider-perforce 156 | ${maven.scm.version} 157 | 158 | 159 | org.apache.maven.scm 160 | maven-scm-provider-hg 161 | ${maven.scm.version} 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | maven-clean-plugin 170 | 3.0.0 171 | 172 | 173 | maven-compiler-plugin 174 | 3.7.0 175 | 176 | 177 | maven-deploy-plugin 178 | 2.8.2 179 | 180 | 181 | maven-gpg-plugin 182 | 1.6 183 | 184 | 185 | maven-install-plugin 186 | 2.5.2 187 | 188 | 189 | maven-invoker-plugin 190 | 3.0.1 191 | 192 | 193 | maven-jar-plugin 194 | 3.0.2 195 | 196 | 197 | maven-plugin-plugin 198 | 3.5.1 199 | 200 | true 201 | 202 | 203 | 204 | mojo-descriptor 205 | 206 | descriptor 207 | 208 | 209 | 210 | help-mojo 211 | 212 | helpmojo 213 | 214 | 215 | 216 | 217 | 218 | maven-release-plugin 219 | 2.5.3 220 | 221 | 222 | maven-resources-plugin 223 | 3.0.2 224 | 225 | 226 | maven-site-plugin 227 | 3.7 228 | 229 | 230 | maven-surefire-plugin 231 | 2.20.1 232 | 233 | 234 | org.codehaus.mojo 235 | mrm-maven-plugin 236 | 1.1.0 237 | 238 | repository.proxy.url 239 | 240 | 241 | 242 | 243 | 244 | 245 | maven-compiler-plugin 246 | 247 | 1.7 248 | 1.7 249 | -g 250 | 251 | 252 | 253 | org.codehaus.mojo 254 | mrm-maven-plugin 255 | 256 | 257 | 258 | start 259 | stop 260 | 261 | 262 | 263 | 264 | 265 | maven-gpg-plugin 266 | 267 | true 268 | ${env.GPG_PASSPHRASE} 269 | 270 | --batch 271 | --pinentry-mode 272 | loopback 273 | 274 | 275 | 276 | 277 | maven-invoker-plugin 278 | 279 | 280 | integration-test 281 | 282 | install 283 | run 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | src/it 293 | ${project.build.directory}/it 294 | ${project.build.directory}/local-repo 295 | src/it/settings.xml 296 | true 297 | true 298 | 299 | */pom.xml 300 | 301 | verify.bsh 302 | 303 | ${repository.proxy.url} 304 | 305 | -Xmx256m 306 | 307 | 308 | 309 | maven-release-plugin 310 | 311 | true 312 | false 313 | validate 314 | 315 | 316 | 317 | com.github.stephenc.continuous 318 | git-timestamp-maven-plugin 319 | 1.40 320 | 321 | x-SNAPSHOT 322 | VERSION.txt 323 | TAG_NAME.txt 324 | false 325 | 326 | 327 | 328 | org.sonatype.plugins 329 | nexus-staging-maven-plugin 330 | 1.6.6 331 | true 332 | 333 | sonatype-nexus-staging 334 | https://oss.sonatype.org/ 335 | c3a098f3fdc7d 336 | 337 | 338 | 339 | 340 | 341 | 342 | -------------------------------------------------------------------------------- /src/it/release/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 4.0.0 20 | localhost 21 | smokes 22 | 1.0-SNAPSHOT 23 | jar 24 | smokes 25 | Basic smoke test 26 | 27 | 28 | scm:git:git://github.com/stephenc/git-timestamp-maven-plugin.git 29 | scm:git:git@github.com:stephenc/git-timestamp-maven-plugin.git 30 | http://github.com/stephenc/git-timestamp-maven-plugin/tree/master/ 31 | HEAD 32 | 33 | 34 | 35 | UTF-8 36 | UTF-8 37 | UTF-8 38 | 39 | 40 | 41 | 42 | commons-io 43 | commons-io 44 | 2.5 45 | 46 | 47 | junit 48 | junit 49 | 4.12 50 | test 51 | 52 | 53 | 54 | 55 | 56 | 57 | src/filtered/resources 58 | true 59 | 60 | 61 | 62 | 63 | @project.groupId@ 64 | @project.artifactId@ 65 | @project.version@ 66 | 67 | 68 | 69 | setup-release 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/it/release/src/filtered/resources/version.filtered.txt: -------------------------------------------------------------------------------- 1 | ${releaseVersion} 2 | -------------------------------------------------------------------------------- /src/it/release/src/invoker.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018 Stephen Connolly. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | invoker.goals=test 17 | -------------------------------------------------------------------------------- /src/it/release/src/test/java/it/VerificationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Stephen Connolly. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package it; 17 | 18 | import java.io.InputStream; 19 | import org.apache.commons.io.IOUtils; 20 | import org.junit.Test; 21 | 22 | import static org.hamcrest.CoreMatchers.is; 23 | import static org.hamcrest.CoreMatchers.not; 24 | import static org.hamcrest.CoreMatchers.notNullValue; 25 | import static org.junit.Assert.assertThat; 26 | 27 | public class VerificationTest { 28 | @Test 29 | public void versions() throws Exception { 30 | InputStream stream = getClass().getResourceAsStream("/version.filtered.txt"); 31 | assertThat(stream, notNullValue()); 32 | String versionFromProperty; 33 | try { 34 | versionFromProperty = IOUtils.toString(stream, "UTF-8"); 35 | } finally { 36 | IOUtils.closeQuietly(stream); 37 | } 38 | assertThat(versionFromProperty.trim(), not(is("${releaseVersion}"))); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/it/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | mrm-maven-plugin 21 | Mock Repository Manager 22 | @repository.proxy.url@ 23 | * 24 | 25 | 26 | 27 | 28 | it-repo 29 | 30 | true 31 | 32 | 33 | 34 | snapshots 35 | @repository.proxy.url@ 36 | 37 | true 38 | ignore 39 | never 40 | 41 | 42 | true 43 | ignore 44 | always 45 | 46 | 47 | 48 | 49 | 50 | snapshots 51 | @repository.proxy.url@ 52 | 53 | true 54 | ignore 55 | never 56 | 57 | 58 | true 59 | ignore 60 | always 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/it/smokes/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 4.0.0 20 | localhost 21 | smokes 22 | 1.0-SNAPSHOT 23 | jar 24 | smokes 25 | Basic smoke test 26 | 27 | 28 | scm:git:git://github.com/stephenc/git-timestamp-maven-plugin.git 29 | scm:git:git@github.com:stephenc/git-timestamp-maven-plugin.git 30 | http://github.com/stephenc/git-timestamp-maven-plugin/tree/master/ 31 | HEAD 32 | 33 | 34 | 35 | UTF-8 36 | UTF-8 37 | UTF-8 38 | 39 | 40 | 41 | 42 | commons-io 43 | commons-io 44 | 2.5 45 | 46 | 47 | junit 48 | junit 49 | 4.12 50 | test 51 | 52 | 53 | 54 | 55 | 56 | 57 | src/filtered/resources 58 | true 59 | 60 | 61 | 62 | 63 | @project.groupId@ 64 | @project.artifactId@ 65 | @project.version@ 66 | 67 | 68 | 69 | timestamp 70 | 71 | 72 | 73 | 74 | build.timestamp 75 | ${basedir}/target/classes/timestamp.txt 76 | build.version 77 | ${basedir}/target/classes/version.txt 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/it/smokes/src/filtered/resources/timestamp.filtered.txt: -------------------------------------------------------------------------------- 1 | ${build.timestamp} 2 | -------------------------------------------------------------------------------- /src/it/smokes/src/filtered/resources/version.filtered.txt: -------------------------------------------------------------------------------- 1 | ${build.version} 2 | -------------------------------------------------------------------------------- /src/it/smokes/src/invoker.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018 Stephen Connolly. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | invoker.goals=test 17 | -------------------------------------------------------------------------------- /src/it/smokes/src/test/java/it/VerificationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Stephen Connolly. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package it; 17 | 18 | import java.io.InputStream; 19 | import org.apache.commons.io.IOUtils; 20 | import org.junit.Test; 21 | 22 | import static org.hamcrest.CoreMatchers.is; 23 | import static org.hamcrest.CoreMatchers.notNullValue; 24 | import static org.junit.Assert.assertThat; 25 | 26 | public class VerificationTest { 27 | @Test 28 | public void timestamps() throws Exception { 29 | InputStream stream = getClass().getResourceAsStream("/timestamp.txt"); 30 | assertThat(stream, notNullValue()); 31 | String timestampFromFile; 32 | try { 33 | timestampFromFile = IOUtils.toString(stream, "UTF-8"); 34 | } finally { 35 | IOUtils.closeQuietly(stream); 36 | } 37 | stream = getClass().getResourceAsStream("/timestamp.filtered.txt"); 38 | assertThat(stream, notNullValue()); 39 | String timestampFromProperty; 40 | try { 41 | timestampFromProperty = IOUtils.toString(stream, "UTF-8"); 42 | } finally { 43 | IOUtils.closeQuietly(stream); 44 | } 45 | assertThat(timestampFromFile.trim(), is(timestampFromProperty.trim())); 46 | } 47 | 48 | @Test 49 | public void versions() throws Exception { 50 | InputStream stream = getClass().getResourceAsStream("/version.txt"); 51 | assertThat(stream, notNullValue()); 52 | String versionFromFile; 53 | try { 54 | versionFromFile = IOUtils.toString(stream, "UTF-8"); 55 | } finally { 56 | IOUtils.closeQuietly(stream); 57 | } 58 | stream = getClass().getResourceAsStream("/version.filtered.txt"); 59 | assertThat(stream, notNullValue()); 60 | String versionFromProperty; 61 | try { 62 | versionFromProperty = IOUtils.toString(stream, "UTF-8"); 63 | } finally { 64 | IOUtils.closeQuietly(stream); 65 | } 66 | assertThat(versionFromFile.trim(), is(versionFromProperty.trim())); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/github/stephenc/continuous/gittimestamp/AbstractGitOpsMojo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Stephen Connolly 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.stephenc.continuous.gittimestamp; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import org.apache.commons.io.FileUtils; 22 | import org.apache.commons.lang.StringUtils; 23 | import org.apache.maven.plugin.AbstractMojo; 24 | import org.apache.maven.plugin.MojoExecutionException; 25 | import org.apache.maven.plugin.MojoFailureException; 26 | import org.apache.maven.plugins.annotations.Component; 27 | import org.apache.maven.plugins.annotations.Parameter; 28 | import org.apache.maven.project.MavenProject; 29 | import org.apache.maven.scm.ScmException; 30 | import org.apache.maven.scm.manager.NoSuchScmProviderException; 31 | import org.apache.maven.scm.manager.ScmManager; 32 | import org.apache.maven.scm.provider.ScmProvider; 33 | import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils; 34 | import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; 35 | import org.apache.maven.scm.repository.ScmRepository; 36 | import org.apache.maven.scm.repository.ScmRepositoryException; 37 | import org.codehaus.plexus.util.cli.CommandLineUtils; 38 | import org.codehaus.plexus.util.cli.Commandline; 39 | 40 | /** 41 | * Base class for the GitOps mojos. 42 | */ 43 | public abstract class AbstractGitOpsMojo extends AbstractMojo { 44 | /** 45 | * The commit, branch or tag name to use as the "zero" revision. Helpful if you want to reset numbering for a 46 | * branch, e.g. if you move from {@code 1.x} to {@code 2.x} you may want the {@code x} numbers for {@code 2.x} to 47 | * restart, in which case on the {@code 2.x} branch you would specify the first commit in {@code 2.x} as the {@link 48 | * #referenceCommit}. 49 | * 50 | * @since 1.47 51 | */ 52 | @Parameter 53 | protected String referenceCommit; 54 | /** 55 | * Controls which SCM URL to prefer for querying, the {@code xpath:/project/scm/developerConnection} or the {@code 56 | * xpath:/project/scm/connection}. 57 | * 58 | * @since 1.29 59 | */ 60 | @Parameter(property = "preferDeveloperconnection", defaultValue = "true") 61 | protected boolean preferDeveloperConnection; 62 | /** 63 | * The character encoding scheme to be applied when writing files. 64 | */ 65 | @Parameter(defaultValue = "${project.build.outputEncoding}") 66 | protected String encoding; 67 | @Parameter(defaultValue = "${basedir}", readonly = true) 68 | protected File basedir; 69 | @Component 70 | protected ScmManager scmManager; 71 | @Parameter(defaultValue = "${project.scm.connection}", readonly = true) 72 | protected String scmUrl; 73 | @Parameter(defaultValue = "${project.scm.developerConnection}", readonly = true) 74 | protected String scmDeveloperUrl; 75 | @Parameter(defaultValue = "${project}", readonly = true) 76 | protected MavenProject project; 77 | 78 | protected void writeFile(File fileName, String value) throws IOException { 79 | if (fileName != null) { 80 | fileName.getParentFile().mkdirs(); 81 | getLog().info("Writing '" + value + "' to " + fileName); 82 | FileUtils.write(fileName, value + "\n", encoding); 83 | } 84 | } 85 | 86 | protected void setProperty(String propertyName, String propertyValue) { 87 | if (StringUtils.isNotBlank(propertyName)) { 88 | getLog().info("Setting property '" + propertyName + "' to '" + propertyValue + "'"); 89 | project.getProperties().setProperty(propertyName, propertyValue); 90 | } 91 | } 92 | 93 | protected long getCurrentBranchCommitCount() 94 | throws ScmException, MojoExecutionException { 95 | Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(basedir, "rev-list"); 96 | cl.createArg().setValue("--count"); 97 | if (StringUtils.isBlank(referenceCommit)) { 98 | cl.createArg().setValue("HEAD"); 99 | } else { 100 | cl.createArg().setValue(referenceCommit + "..HEAD"); 101 | } 102 | CommandLineUtils.StringStreamConsumer countOutput = new CommandLineUtils.StringStreamConsumer(); 103 | GitCommandLineUtils.execute(cl, countOutput, logWarnConsumer(), new GitCommandLineLogger(this)); 104 | try { 105 | return Long.parseLong(StringUtils.defaultIfBlank(countOutput.getOutput().trim(), "0")); 106 | } catch (NumberFormatException e) { 107 | throw new MojoExecutionException( 108 | "Could not parse revision count from 'rev-list --count' output: " + countOutput.getOutput(), 109 | e 110 | ); 111 | } 112 | } 113 | 114 | protected ScmRepository getScmRepository() throws ScmRepositoryException, NoSuchScmProviderException { 115 | String scmUrl = preferDeveloperConnection 116 | ? (scmDeveloperUrl == null || scmDeveloperUrl.isEmpty() ? this.scmUrl : scmDeveloperUrl) 117 | : (this.scmUrl == null || this.scmUrl.isEmpty() ? scmDeveloperUrl : this.scmUrl); 118 | return scmManager.makeScmRepository(scmUrl); 119 | } 120 | 121 | protected ScmProvider getValidatedScmProvider(ScmRepository repository) 122 | throws NoSuchScmProviderException, MojoFailureException { 123 | ScmProvider provider = scmManager.getProviderByRepository(repository); 124 | if (!GitScmProviderRepository.PROTOCOL_GIT.equals(provider.getScmType())) { 125 | throw new MojoFailureException("Only Git SCM type is supported"); 126 | } 127 | return provider; 128 | } 129 | 130 | protected CommandLineUtils.StringStreamConsumer logWarnConsumer() { 131 | return new CommandLineUtils.StringStreamConsumer() { 132 | @Override 133 | public void consumeLine(String line) { 134 | super.consumeLine(line); 135 | getLog().warn(line); 136 | } 137 | }; 138 | } 139 | protected CommandLineUtils.StringStreamConsumer logInfoConsumer() { 140 | return new CommandLineUtils.StringStreamConsumer() { 141 | @Override 142 | public void consumeLine(String line) { 143 | super.consumeLine(line); 144 | getLog().info(line); 145 | } 146 | }; 147 | } 148 | protected CommandLineUtils.StringStreamConsumer logDebugConsumer() { 149 | return new CommandLineUtils.StringStreamConsumer() { 150 | @Override 151 | public void consumeLine(String line) { 152 | super.consumeLine(line); 153 | getLog().debug(line); 154 | } 155 | }; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/com/github/stephenc/continuous/gittimestamp/GitCommandLineLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Stephen Connolly 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.stephenc.continuous.gittimestamp; 18 | 19 | import org.apache.maven.plugin.AbstractMojo; 20 | import org.apache.maven.scm.log.ScmLogger; 21 | 22 | /** 23 | * Log adapter. 24 | */ 25 | class GitCommandLineLogger implements ScmLogger { 26 | 27 | private AbstractMojo mojo; 28 | 29 | public GitCommandLineLogger(AbstractMojo mojo) { 30 | this.mojo = mojo; 31 | } 32 | 33 | @Override 34 | public boolean isDebugEnabled() { 35 | return mojo.getLog().isDebugEnabled(); 36 | } 37 | 38 | @Override 39 | public void debug(String content) { 40 | mojo.getLog().debug(content); 41 | } 42 | 43 | @Override 44 | public void debug(String content, Throwable error) { 45 | mojo.getLog().debug(content, error); 46 | } 47 | 48 | @Override 49 | public void debug(Throwable error) { 50 | mojo.getLog().debug(error); 51 | } 52 | 53 | @Override 54 | public boolean isInfoEnabled() { 55 | return mojo.getLog().isInfoEnabled(); 56 | } 57 | 58 | @Override 59 | public void info(String content) { 60 | mojo.getLog().info(content); 61 | } 62 | 63 | @Override 64 | public void info(String content, Throwable error) { 65 | mojo.getLog().info(content, error); 66 | } 67 | 68 | @Override 69 | public void info(Throwable error) { 70 | mojo.getLog().info(error); 71 | } 72 | 73 | @Override 74 | public boolean isWarnEnabled() { 75 | return mojo.getLog().isWarnEnabled(); 76 | } 77 | 78 | @Override 79 | public void warn(String content) { 80 | mojo.getLog().warn(content); 81 | } 82 | 83 | @Override 84 | public void warn(String content, Throwable error) { 85 | mojo.getLog().warn(content, error); 86 | } 87 | 88 | @Override 89 | public void warn(Throwable error) { 90 | mojo.getLog().warn(error); 91 | } 92 | 93 | @Override 94 | public boolean isErrorEnabled() { 95 | return mojo.getLog().isErrorEnabled(); 96 | } 97 | 98 | @Override 99 | public void error(String content) { 100 | mojo.getLog().error(content); 101 | } 102 | 103 | @Override 104 | public void error(String content, Throwable error) { 105 | mojo.getLog().error(content, error); 106 | } 107 | 108 | @Override 109 | public void error(Throwable error) { 110 | mojo.getLog().error(error); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/github/stephenc/continuous/gittimestamp/ReleaseMojo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Stephen Connolly 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.stephenc.continuous.gittimestamp; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.util.Arrays; 22 | import java.util.HashSet; 23 | import java.util.Iterator; 24 | import java.util.List; 25 | import java.util.Properties; 26 | import java.util.Set; 27 | import org.apache.commons.lang.StringUtils; 28 | import org.apache.maven.plugin.MojoExecutionException; 29 | import org.apache.maven.plugin.MojoFailureException; 30 | import org.apache.maven.plugins.annotations.LifecyclePhase; 31 | import org.apache.maven.plugins.annotations.Mojo; 32 | import org.apache.maven.plugins.annotations.Parameter; 33 | import org.apache.maven.project.MavenProject; 34 | import org.apache.maven.scm.ScmException; 35 | import org.apache.maven.scm.manager.NoSuchScmProviderException; 36 | import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils; 37 | import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; 38 | import org.apache.maven.scm.repository.ScmRepository; 39 | import org.codehaus.plexus.interpolation.InterpolationException; 40 | import org.codehaus.plexus.interpolation.Interpolator; 41 | import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor; 42 | import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; 43 | import org.codehaus.plexus.interpolation.RecursionInterceptor; 44 | import org.codehaus.plexus.interpolation.StringSearchInterpolator; 45 | import org.codehaus.plexus.util.cli.Commandline; 46 | import org.codehaus.plexus.util.cli.StreamConsumer; 47 | 48 | /** 49 | * Generates a release version based on the number of commits in the current Git branch and available tags. This mojo is 50 | * designed to be invoked before {@code release:prepare}. 51 | * 52 | * @since 1.39 53 | */ 54 | @Mojo(name = "setup-release", 55 | inheritByDefault = false, 56 | aggregator = true, 57 | defaultPhase = LifecyclePhase.INITIALIZE, 58 | requiresProject = true, 59 | threadSafe = true) 60 | public class ReleaseMojo extends AbstractGitOpsMojo { 61 | private static final String REFS_TAGS = "refs/tags/"; 62 | /** 63 | * The name of the property to populate with the release version. 64 | */ 65 | @Parameter(defaultValue = "releaseVersion") 66 | private String releaseProperty; 67 | /** 68 | * The name of the property to populate with the follow-on development version. 69 | */ 70 | @Parameter(defaultValue = "developmentVersion") 71 | private String developmentProperty; 72 | /** 73 | * The name of the property to populate with the tag name. 74 | * 75 | * @since 1.29 76 | */ 77 | @Parameter(defaultValue = "tag") 78 | private String tagNameProperty; 79 | /** 80 | * When {@code true} will disable the check of whether setting {@code autoVersionSubmodules} makes sense. 81 | * 82 | * @since 1.29 83 | */ 84 | @Parameter(property = "skipAutoVersionSubmodulesDetection") 85 | private boolean skipAutoVersionSubmodulesDetection; 86 | /** 87 | * By default, the first attempt at any revision version will have the {@code .0} implicit, only for repeats do we 88 | * add the {@code .1}, {@code .2}, etc in order to disambiguate, setting this property to {@code true} will disable 89 | * the special casing for {@code .0} and thus it will always be present. For example, if the project version is 90 | * {@code 1-SNAPSHOT} and there are 57 commits on the current branch: 91 | *
92 | *
{@code skipAutoVersionSubmodulesDetection == false}
93 | *
The candidate versions will be: 94 | *
95 | *
{@code skipAutoVersionSubmodulesDetection == true}
96 | *
The candidate versions will be: 97 | *
98 | *
99 | * 100 | * @since 1.39 101 | */ 102 | @Parameter(property = "alwaysIncludeRepeatCount") 103 | private boolean alwaysIncludeRepeatCount; 104 | /** 105 | * The text in the version to be replaced. Normally you will want {@code -SNAPSHOT} but if you are using a version 106 | * number that indicates replacement such as {@code 1.x-SNAPSHOT} then you may want to use {@code x-SNAPSHOT} so 107 | * that the {@code x} is replaced. 108 | */ 109 | @Parameter(defaultValue = "-SNAPSHOT", property = "snapshotText") 110 | private String snapshotText; 111 | /** 112 | * Disables querying the remote tags and instead only queries local tags. 113 | */ 114 | @Parameter(property = "localTags") 115 | private boolean localTags; 116 | /** 117 | * Format to use when generating the tag name if none is specified. Mirrors {@code release:prepare}'s property. 118 | */ 119 | @Parameter(defaultValue = "@{project.artifactId}-@{project.version}", property = "tagNameFormat") 120 | private String tagNameFormat; 121 | /** 122 | * If defined, the name of the file to populate with the project version followed by a newline. 123 | */ 124 | @Parameter(property = "releaseVersionFile") 125 | private File releaseVersionFile; 126 | /** 127 | * If defined, the name of the file to populate with the suggested tag name followed by a newline. 128 | */ 129 | @Parameter(property = "tagNameFile") 130 | private File tagNameFile; 131 | 132 | /** 133 | * {@inheritDoc} 134 | */ 135 | @Override 136 | public void execute() throws MojoExecutionException, MojoFailureException { 137 | if (!project.getVersion().endsWith(snapshotText)) { 138 | throw new MojoFailureException("The current project version is \'" + project.getVersion() 139 | + "\' which does not end with the expected text to be replaced: \'" + snapshotText + "\'"); 140 | } 141 | try { 142 | ScmRepository repository = getScmRepository(); 143 | getValidatedScmProvider(repository); 144 | 145 | // now count how many commits on the current branch 146 | final long count = getCurrentBranchCommitCount(); 147 | 148 | final Set tags = new HashSet<>(); 149 | Commandline cl; 150 | StreamConsumer consumer; 151 | if (!localTags && repository.getProviderRepository() instanceof GitScmProviderRepository) { 152 | cl = GitCommandLineUtils.getBaseGitCommandLine(basedir, "ls-remote"); 153 | cl.createArg().setValue("--tags"); 154 | cl.createArg().setValue("--quiet"); 155 | cl.createArg().setValue(((GitScmProviderRepository) repository.getProviderRepository()).getFetchUrl()); 156 | consumer = new LsRemoteTagsConsumer(tags); 157 | } else { 158 | cl = GitCommandLineUtils.getBaseGitCommandLine(basedir, "tag"); 159 | cl.createArg().setValue("--list"); 160 | consumer = new TagListConsumer(tags); 161 | } 162 | GitCommandLineUtils.execute(cl, consumer, logWarnConsumer(), new GitCommandLineLogger(this)); 163 | 164 | String bareVersion = StringUtils.removeEnd(project.getVersion(), snapshotText); 165 | if (!bareVersion.endsWith(".") && !bareVersion.endsWith("-")) { 166 | // insert a separator if none present 167 | bareVersion = bareVersion + "."; 168 | } 169 | final String baseVersion = bareVersion + count; 170 | Iterator suggestedVersion = new CandidateVersionsIterator(baseVersion, alwaysIncludeRepeatCount); 171 | String version; 172 | String suggestedTagName; 173 | while (true) { 174 | version = suggestedVersion.next(); 175 | suggestedTagName = tagNameFromVersion(version); 176 | 177 | if (!tags.contains(suggestedTagName)) { 178 | getLog().info( 179 | "Could not find a tag called " + suggestedTagName + " recommending version " + version); 180 | break; 181 | } 182 | getLog().debug("Skipping " + version + " as there is already a tag named " + suggestedTagName); 183 | } 184 | getLog().debug("Known tags: " + tags); 185 | 186 | // Ok let's set up the properties for release:prepare 187 | 188 | // The release version 189 | setProperty(releaseProperty, version); 190 | writeFile(this.releaseVersionFile, version); 191 | 192 | // The follow-on development version 193 | setProperty(developmentProperty, project.getVersion()); 194 | 195 | // The tag 196 | setProperty(this.tagNameProperty, suggestedTagName); 197 | writeFile(this.tagNameFile, suggestedTagName); 198 | 199 | // Now can we help and set autoVersionSubmodules? 200 | if (skipAutoVersionSubmodulesDetection) { 201 | getLog().debug("autoVersionSubmodules detection disabled"); 202 | return; 203 | } 204 | if (!project.isExecutionRoot()) { 205 | getLog().info("Project is not execution root, autoVersionSubmodules detection does not apply"); 206 | return; 207 | } 208 | for (MavenProject p : project.getCollectedProjects()) { 209 | if (!project.getVersion().equals(p.getVersion())) { 210 | getLog().warn("Reactor project " + p.getGroupId() + ":" + p.getArtifactId() + " has version " 211 | + p.getVersion() + " which is not the same as " + project.getVersion() 212 | + " thus autoVersionSubmodules cannot be assumed true"); 213 | return; 214 | } 215 | } 216 | getLog().info("All reactor projects share the same version: " + project.getVersion()); 217 | setProperty("autoVersionSubmodules", "true"); 218 | } catch (NoSuchScmProviderException e) { 219 | throw new MojoFailureException("Unknown SCM URL: " + scmUrl, e); 220 | } catch (ScmException | IOException e) { 221 | throw new MojoExecutionException(e.getMessage(), e); 222 | } 223 | } 224 | 225 | private String tagNameFromVersion(String version) throws MojoExecutionException { 226 | Interpolator interpolator = new StringSearchInterpolator("@{", "}"); 227 | List possiblePrefixes = Arrays.asList("project", "pom"); 228 | Properties values = new Properties(); 229 | values.setProperty("artifactId", project.getArtifactId()); 230 | values.setProperty("groupId", project.getGroupId()); 231 | values.setProperty("version", version); 232 | interpolator.addValueSource(new PrefixedPropertiesValueSource(possiblePrefixes, values, true)); 233 | RecursionInterceptor recursionInterceptor = new PrefixAwareRecursionInterceptor(possiblePrefixes); 234 | try { 235 | return interpolator.interpolate(tagNameFormat, recursionInterceptor); 236 | } catch (InterpolationException e) { 237 | throw new MojoExecutionException( 238 | "Could not interpolate specified tag name format: " + tagNameFormat, e); 239 | } 240 | } 241 | 242 | 243 | private static class CandidateVersionsIterator implements Iterator { 244 | 245 | private final String baseVersion; 246 | private final boolean alwaysIncludeRepeatCount; 247 | private long patch; 248 | 249 | public CandidateVersionsIterator(String baseVersion, boolean alwaysIncludeRepeatCount) { 250 | this.baseVersion = baseVersion; 251 | this.alwaysIncludeRepeatCount = alwaysIncludeRepeatCount; 252 | } 253 | 254 | @Override 255 | public boolean hasNext() { 256 | return true; 257 | } 258 | 259 | @Override 260 | public String next() { 261 | try { 262 | return alwaysIncludeRepeatCount || patch > 0 ? baseVersion + "." + patch : baseVersion; 263 | } finally { 264 | patch++; 265 | } 266 | } 267 | 268 | @Override 269 | public void remove() { 270 | throw new UnsupportedOperationException(); 271 | } 272 | } 273 | 274 | private static class TagListConsumer implements StreamConsumer { 275 | private final Set tags; 276 | 277 | public TagListConsumer(Set tags) { 278 | this.tags = tags; 279 | } 280 | 281 | @Override 282 | public void consumeLine(String line) { 283 | line.trim(); 284 | if (!line.isEmpty()) { 285 | tags.add(line); 286 | } 287 | } 288 | } 289 | 290 | private static class LsRemoteTagsConsumer implements StreamConsumer { 291 | private final Set tags; 292 | 293 | public LsRemoteTagsConsumer(Set tags) { 294 | this.tags = tags; 295 | } 296 | 297 | @Override 298 | public void consumeLine(String line) { 299 | line.trim(); 300 | if (!line.isEmpty()) { 301 | int index = line.indexOf(REFS_TAGS); 302 | if (index != -1 && line.matches("^[0-9a-fA-F]{40}\\s+refs/tags/.*$")) { 303 | if (line.endsWith("{}")) { 304 | line = line.substring(0, line.length() - 2); 305 | } 306 | tags.add(line.substring(index + REFS_TAGS.length())); 307 | } 308 | } 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/main/java/com/github/stephenc/continuous/gittimestamp/TimestampMojo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-9 Stephen Connolly. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.stephenc.continuous.gittimestamp; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.text.SimpleDateFormat; 22 | import java.util.Date; 23 | import java.util.regex.Matcher; 24 | import java.util.regex.Pattern; 25 | import org.apache.commons.lang.StringUtils; 26 | import org.apache.maven.plugin.MojoExecutionException; 27 | import org.apache.maven.plugin.MojoFailureException; 28 | import org.apache.maven.plugins.annotations.LifecyclePhase; 29 | import org.apache.maven.plugins.annotations.Mojo; 30 | import org.apache.maven.plugins.annotations.Parameter; 31 | import org.apache.maven.scm.ScmException; 32 | import org.apache.maven.scm.ScmFile; 33 | import org.apache.maven.scm.ScmFileSet; 34 | import org.apache.maven.scm.command.status.StatusScmResult; 35 | import org.apache.maven.scm.manager.NoSuchScmProviderException; 36 | import org.apache.maven.scm.provider.ScmProvider; 37 | import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils; 38 | import org.apache.maven.scm.repository.ScmRepository; 39 | import org.codehaus.plexus.util.cli.Commandline; 40 | import org.codehaus.plexus.util.cli.StreamConsumer; 41 | 42 | /** 43 | * Generates a timestamp version based on the number of commits in the current Git branch and the last modified 44 | * timestamp of all the repository files 45 | */ 46 | @Mojo(name = "timestamp", 47 | aggregator = false, 48 | defaultPhase = LifecyclePhase.INITIALIZE, 49 | requiresProject = true, 50 | threadSafe = true) 51 | public class TimestampMojo extends AbstractGitOpsMojo { 52 | private static final SimpleDateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyyMMdd.HHmmss"); 53 | private static final Pattern SNAPSHOT_PATTERN = Pattern.compile( 54 | "^(.*-)?((?:SNAPSHOT)|(?:\\d{4}[0-1]\\d[0-3]\\d\\.[0-2]\\d[0-6]\\d[0-6]\\d-\\d+))$" 55 | ); 56 | /** 57 | * If defined, the name of the property to populate with the raw timestamp, which will be in the format 58 | * {@code yyyyMMdd.HHmmss-NNNN} 59 | */ 60 | @Parameter 61 | private String timestampProperty; 62 | /** 63 | * If defined, the name of the property to populate with the project version. 64 | *
    65 | *
  • 66 | * If the project version is a release, the value is determined by {@link #versionTimestampReleases}: 67 | *
      68 | *
    • when {@code false} the project version will be passed through unmodified
    • 69 | *
    • when {@code true} the timestamp will be appended to the project version
    • 70 | *
    71 | *
  • 72 | *
  • 73 | * If the project version is a snapshot, the value is determined by {@link #versionTimestampSnapshots}: 74 | *
      75 | *
    • when {@code false} the project version will be passed through unmodified
    • 76 | *
    • when {@code true} the {@code -SNAPSHOT} will be replaced with the timestamp
    • 77 | *
    78 | *
  • 79 | *
80 | */ 81 | @Parameter 82 | private String versionProperty; 83 | @Parameter(defaultValue = "false") 84 | private boolean versionTimestampReleases; 85 | @Parameter(defaultValue = "true") 86 | private boolean versionTimestampSnapshots; 87 | /** 88 | * If defined, the name of the file to populate with the raw timestamp, which will be in the format 89 | * {@code yyyyMMdd.HHmmss-NNNN} followed by a newline. 90 | */ 91 | @Parameter 92 | private File timestampFile; 93 | /** 94 | * If defined, the name of the file to populate with the project version followed by a newline. 95 | *
    96 | *
  • 97 | * If the project version is a release, the value is determined by {@link #versionTimestampReleases}: 98 | *
      99 | *
    • when {@code false} the project version will be passed through unmodified
    • 100 | *
    • when {@code true} the timestamp will be appended to the project version
    • 101 | *
    102 | *
  • 103 | *
  • 104 | * If the project version is a snapshot, the value is determined by {@link #versionTimestampSnapshots}: 105 | *
      106 | *
    • when {@code false} the project version will be passed through unmodified
    • 107 | *
    • when {@code true} the {@code -SNAPSHOT} will be replaced with the timestamp
    • 108 | *
    109 | *
  • 110 | *
111 | */ 112 | @Parameter 113 | private File versionFile; 114 | /** 115 | * Set this property to {@code true} to inject the commit count prior to the {@link #snapshotText} in snapshot 116 | * versions. Normally, only the {@code SNAPSHOT} part of the project version is replaced by the timestamp, this is 117 | * because such a substitution will be idempotent under the Maven version number parsing rules (as {@code 118 | * 1-SNAPSHOT} is considered the same as {@code 1-20190322.100407-39}). Setting this property to {@code true} means 119 | * that the commit count will also be injected so that we would have either {@code 1.39-20190322.100407-39} 120 | * (no modified files in the workspace) or {@code 1.40-20190322.100407-39} (modified files in the workspace) as the 121 | * version. In other words, setting this property to {@code true} will mean that the version honours the expected 122 | * release version that would be produced by {@link ReleaseMojo} while also reflecting the fact that this version 123 | * is only a SNAPSHOT on the road to that release. 124 | *
125 | * NOTE: Maven will not recognise a version output as being equivalent to the project version when this 126 | * property is set to {@code true}, but if you are using this version in your own code it may be useful. 127 | * 128 | * @since 1.39 129 | */ 130 | @Parameter(defaultValue = "false", property = "versionIncludesCommitCount") 131 | private boolean versionIncludesCommitCount; 132 | /** 133 | * If {@link #versionIncludesCommitCount} is {@code true} then this is the text in the version to be replaced by 134 | * the commit count. Normally you will want {@code -SNAPSHOT} but if you are using a version number that 135 | * indicates replacement such as {@code 1.x-SNAPSHOT} then you may want to use {@code x-SNAPSHOT} so 136 | * that the {@code x} is replaced. 137 | */ 138 | @Parameter(defaultValue = "-SNAPSHOT", property = "snapshotText") 139 | private String snapshotText; 140 | 141 | /** 142 | * {@inheritDoc} 143 | */ 144 | @Override 145 | public void execute() throws MojoExecutionException, MojoFailureException { 146 | try { 147 | // first check that we are using git 148 | ScmRepository repository = getScmRepository(); 149 | ScmProvider provider = getValidatedScmProvider(repository); 150 | 151 | // now get the last modified timestamp 152 | final long[] lastModified = new long[1]; 153 | lastModified[0] = project.getFile().lastModified(); 154 | Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(basedir, "ls-files"); 155 | GitCommandLineUtils.execute(cl, new StreamConsumer() { 156 | @Override 157 | public void consumeLine(String s) { 158 | lastModified[0] = Math.max(lastModified[0], new File(basedir, s).lastModified()); 159 | } 160 | }, logDebugConsumer(), new GitCommandLineLogger(this)); 161 | StatusScmResult status = provider.status(repository, new ScmFileSet(basedir)); 162 | for (ScmFile f: status.getChangedFiles()) { 163 | lastModified[0] = Math.max(lastModified[0], new File(basedir, f.getPath()).lastModified()); 164 | } 165 | 166 | // now count how many commits on the current branch 167 | long count = getCurrentBranchCommitCount(); 168 | 169 | // ok, let's create the timestamp 170 | String timestamp = TIMESTAMP_FORMAT.format(new Date(lastModified[0])) + "-" + count; 171 | String version = project.getVersion(); 172 | Matcher matcher = SNAPSHOT_PATTERN.matcher(version); 173 | if (matcher.matches()) { 174 | if (versionTimestampSnapshots) { 175 | String bareVersion; 176 | if (versionIncludesCommitCount) { 177 | String snapshotVersion = matcher.group(1) + "SNAPSHOT"; 178 | if (StringUtils.endsWith(snapshotVersion, snapshotText)) { 179 | bareVersion = StringUtils.removeEnd(snapshotVersion, snapshotText); 180 | if (!bareVersion.endsWith(".") && !bareVersion.endsWith("-")) { 181 | // insert a separator if none present 182 | bareVersion = bareVersion + "."; 183 | } 184 | bareVersion = bareVersion + (count + (status.getChangedFiles().isEmpty() ? 0 : 1)) + '-'; 185 | } else { 186 | getLog().warn("Project version '" + version + "' normalized to '" + snapshotVersion 187 | + "' does not end with '" + snapshotText + "'"); 188 | bareVersion = matcher.group(1); 189 | } 190 | } else { 191 | bareVersion = matcher.group(1); 192 | } 193 | version = bareVersion + timestamp; 194 | } 195 | } else { 196 | if (versionTimestampReleases) { 197 | version = version + "-" + timestamp; 198 | } 199 | } 200 | getLog().info("Timestamp: " + timestamp); 201 | getLog().info("Version: " + version); 202 | setProperty(timestampProperty, timestamp); 203 | writeFile(timestampFile, timestamp); 204 | setProperty(versionProperty, version); 205 | writeFile(versionFile, version); 206 | } catch (NoSuchScmProviderException e) { 207 | throw new MojoFailureException("Unknown SCM URL: " + scmUrl, e); 208 | } catch (ScmException | IOException e) { 209 | throw new MojoExecutionException(e.getMessage(), e); 210 | } 211 | } 212 | 213 | } 214 | --------------------------------------------------------------------------------