1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.scmpublish;
20
21 import java.io.BufferedReader;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStreamReader;
25 import java.io.OutputStreamWriter;
26 import java.io.PrintWriter;
27 import java.nio.file.Files;
28 import java.nio.file.LinkOption;
29 import java.nio.file.StandardCopyOption;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.Date;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Set;
37
38 import org.apache.commons.io.FileUtils;
39 import org.apache.commons.io.IOUtils;
40 import org.apache.commons.io.filefilter.NameFileFilter;
41 import org.apache.commons.io.filefilter.NotFileFilter;
42 import org.apache.maven.plugin.MojoExecutionException;
43 import org.apache.maven.plugin.MojoFailureException;
44 import org.apache.maven.plugins.annotations.Mojo;
45 import org.apache.maven.plugins.annotations.Parameter;
46 import org.apache.maven.project.MavenProject;
47 import org.apache.maven.shared.utils.logging.MessageUtils;
48 import org.codehaus.plexus.util.MatchPatterns;
49
50
51
52
53
54
55 @Mojo(name = "publish-scm", aggregator = true, requiresProject = false)
56 public class ScmPublishPublishScmMojo extends AbstractScmPublishMojo {
57
58
59
60 @Parameter(property = "scmpublish.content", defaultValue = "${project.build.directory}/staging")
61 private File content;
62
63
64
65 @Parameter(defaultValue = "${project}", readonly = true, required = true)
66 protected MavenProject project;
67
68 private List<File> deleted = new ArrayList<>();
69
70 private List<File> added = new ArrayList<>();
71
72 private List<File> updated = new ArrayList<>();
73
74 private int directories = 0;
75 private int files = 0;
76 private long size = 0;
77
78
79
80
81
82
83
84
85
86
87 private void update(File checkout, File dir, List<String> doNotDeleteDirs) throws IOException {
88 String scmSpecificFilename = scmProvider.getScmSpecificFilename();
89 String[] files = scmSpecificFilename != null
90 ? checkout.list(new NotFileFilter(new NameFileFilter(scmSpecificFilename)))
91 : checkout.list();
92
93 Set<String> checkoutContent = new HashSet<>(Arrays.asList(files));
94 List<String> dirContent = (dir != null) ? Arrays.asList(dir.list()) : Collections.emptyList();
95
96 Set<String> deleted = new HashSet<>(checkoutContent);
97 deleted.removeAll(dirContent);
98
99 MatchPatterns ignoreDeleteMatchPatterns = null;
100 List<String> pathsAsList = new ArrayList<>(0);
101 if (ignorePathsToDelete != null && ignorePathsToDelete.length > 0) {
102 ignoreDeleteMatchPatterns = MatchPatterns.from(ignorePathsToDelete);
103 pathsAsList = Arrays.asList(ignorePathsToDelete);
104 }
105
106 for (String name : deleted) {
107 if (ignoreDeleteMatchPatterns != null && ignoreDeleteMatchPatterns.matches(name, true)) {
108 getLog().debug(name + " match one of the patterns '" + pathsAsList + "': do not add to deleted files");
109 continue;
110 }
111 getLog().debug("file marked for deletion: " + name);
112 File file = new File(checkout, name);
113
114 if ((doNotDeleteDirs != null) && file.isDirectory() && (doNotDeleteDirs.contains(name))) {
115
116 continue;
117 }
118
119 if (file.isDirectory()) {
120 update(file, null, null);
121 }
122 this.deleted.add(file);
123 }
124
125 for (String name : dirContent) {
126 File file = new File(checkout, name);
127 File source = new File(dir, name);
128
129 if (Files.isSymbolicLink(source.toPath())) {
130 if (!checkoutContent.contains(name)) {
131 this.added.add(file);
132 }
133
134
135 copySymLink(source, file);
136 } else if (source.isDirectory()) {
137 directories++;
138 if (!checkoutContent.contains(name)) {
139 this.added.add(file);
140 file.mkdir();
141 }
142
143 update(file, source, null);
144 } else {
145 if (checkoutContent.contains(name)) {
146 this.updated.add(file);
147 } else {
148 this.added.add(file);
149 }
150
151 copyFile(source, file);
152 }
153 }
154 }
155
156
157
158
159
160
161
162
163 private void copySymLink(File srcFile, File destFile) throws IOException {
164 Files.copy(
165 srcFile.toPath(),
166 destFile.toPath(),
167 StandardCopyOption.REPLACE_EXISTING,
168 StandardCopyOption.COPY_ATTRIBUTES,
169 LinkOption.NOFOLLOW_LINKS);
170 }
171
172
173
174
175
176
177
178
179
180 private void copyFile(File srcFile, File destFile) throws IOException {
181 if (requireNormalizeNewlines(srcFile)) {
182 copyAndNormalizeNewlines(srcFile, destFile);
183 } else {
184 FileUtils.copyFile(srcFile, destFile);
185 }
186 files++;
187 size += destFile.length();
188 }
189
190
191
192
193
194
195
196
197 private void copyAndNormalizeNewlines(File srcFile, File destFile) throws IOException {
198 BufferedReader in = null;
199 PrintWriter out = null;
200 try {
201 in = new BufferedReader(new InputStreamReader(Files.newInputStream(srcFile.toPath()), siteOutputEncoding));
202 out = new PrintWriter(new OutputStreamWriter(Files.newOutputStream(destFile.toPath()), siteOutputEncoding));
203
204 for (String line = in.readLine(); line != null; line = in.readLine()) {
205 if (in.ready()) {
206 out.println(line);
207 } else {
208 out.print(line);
209 }
210 }
211
212 out.close();
213 out = null;
214 in.close();
215 in = null;
216 } finally {
217 IOUtils.closeQuietly(out);
218 IOUtils.closeQuietly(in);
219 }
220 }
221
222 public void scmPublishExecute() throws MojoExecutionException, MojoFailureException {
223 if (siteOutputEncoding == null) {
224 getLog().warn("No output encoding, defaulting to UTF-8.");
225 siteOutputEncoding = "utf-8";
226 }
227
228 if (!content.exists()) {
229 throw new MojoExecutionException("Configured content directory does not exist: " + content);
230 }
231
232 if (!content.canRead()) {
233 throw new MojoExecutionException("Can't read content directory: " + content);
234 }
235
236 checkoutExisting();
237
238 final File updateDirectory;
239 if (subDirectory == null) {
240 updateDirectory = checkoutDirectory;
241 } else {
242 updateDirectory = new File(checkoutDirectory, subDirectory);
243
244
245 if (!updateDirectory
246 .toPath()
247 .normalize()
248 .startsWith(checkoutDirectory.toPath().normalize())) {
249 logError("Try to acces outside of the checkout directory with sub-directory: %s", subDirectory);
250 return;
251 }
252
253 if (!updateDirectory.exists()) {
254 updateDirectory.mkdirs();
255 }
256
257 logInfo("Will copy content in sub-directory: %s", subDirectory);
258 }
259
260 try {
261 logInfo("Updating checkout directory with actual content in %s", content);
262 update(
263 updateDirectory,
264 content,
265 (project == null) ? null : project.getModel().getModules());
266 String displaySize = org.apache.commons.io.FileUtils.byteCountToDisplaySize(size);
267 logInfo(
268 "Content consists of " + MessageUtils.buffer().strong("%d directories and %d files = %s"),
269 directories,
270 files,
271 displaySize);
272 } catch (IOException ioe) {
273 throw new MojoExecutionException("Could not copy content to SCM checkout", ioe);
274 }
275
276 logInfo(
277 "Publishing content to SCM will result in "
278 + MessageUtils.buffer().strong("%d addition(s), %d update(s), %d delete(s)"),
279 added.size(),
280 updated.size(),
281 deleted.size());
282
283 if (isDryRun()) {
284 int pos = checkoutDirectory.getAbsolutePath().length() + 1;
285 for (File addedFile : added) {
286 logInfo("- addition %s", addedFile.getAbsolutePath().substring(pos));
287 }
288 for (File updatedFile : updated) {
289 logInfo("- update %s", updatedFile.getAbsolutePath().substring(pos));
290 }
291 for (File deletedFile : deleted) {
292 logInfo("- delete %s", deletedFile.getAbsolutePath().substring(pos));
293 }
294 return;
295 }
296
297 if (!added.isEmpty()) {
298 addFiles(added);
299 }
300
301 if (!deleted.isEmpty()) {
302 deleteFiles(deleted);
303 }
304
305 logInfo("Checking in SCM, starting at " + new Date() + "...");
306 checkinFiles();
307 }
308 }