├── .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 | *
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 |
--------------------------------------------------------------------------------