View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.report.projectinfo;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Locale;
24  
25  import org.apache.maven.doxia.sink.Sink;
26  import org.apache.maven.model.Model;
27  import org.apache.maven.model.Scm;
28  import org.apache.maven.plugin.logging.Log;
29  import org.apache.maven.plugins.annotations.Component;
30  import org.apache.maven.plugins.annotations.Mojo;
31  import org.apache.maven.plugins.annotations.Parameter;
32  import org.apache.maven.scm.manager.ScmManager;
33  import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
34  import org.apache.maven.scm.provider.hg.repository.HgScmProviderRepository;
35  import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
36  import org.apache.maven.scm.repository.ScmRepository;
37  import org.codehaus.plexus.i18n.I18N;
38  import org.codehaus.plexus.util.StringUtils;
39  
40  /**
41   * Generates the Project Source Code Management (SCM) report.
42   *
43   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton </a>
44   * @since 2.0
45   */
46  @Mojo(name = "scm")
47  public class ScmReport extends AbstractProjectInfoReport {
48      // ----------------------------------------------------------------------
49      // Mojo parameters
50      // ----------------------------------------------------------------------
51  
52      /**
53       * Maven SCM Manager.
54       */
55      @Component
56      protected ScmManager scmManager;
57  
58      /**
59       * The directory name to checkout right after the SCM URL.
60       */
61      @Parameter(defaultValue = "${project.artifactId}")
62      private String checkoutDirectoryName;
63  
64      /**
65       * The SCM anonymous connection url respecting the SCM URL Format.
66       *
67       * @see <a href="http://maven.apache.org/scm/scm-url-format.html">SCM URL Format</a>
68       * @since 2.1
69       */
70      @Parameter(defaultValue = "${project.scm.connection}")
71      private String anonymousConnection;
72  
73      /**
74       * The SCM developer connection url respecting the SCM URL Format.
75       *
76       * @see <a href="http://maven.apache.org/scm/scm-url-format.html">SCM URL Format</a>
77       * @since 2.1
78       */
79      @Parameter(defaultValue = "${project.scm.developerConnection}")
80      private String developerConnection;
81  
82      /**
83       * The SCM web access url.
84       *
85       * @since 2.1
86       */
87      @Parameter(defaultValue = "${project.scm.url}")
88      private String webAccessUrl;
89  
90      /**
91       * The SCM tag.
92       *
93       * @since 2.8
94       */
95      @Parameter(defaultValue = "${project.scm.tag}")
96      private String scmTag;
97  
98      // ----------------------------------------------------------------------
99      // Public methods
100     // ----------------------------------------------------------------------
101 
102     @Override
103     public boolean canGenerateReport() {
104         boolean result = super.canGenerateReport();
105         if (result && skipEmptyReport) {
106             Scm scm = getProject().getModel().getScm();
107             result = scm != null;
108 
109             if (result
110                     && (anonymousConnection == null || anonymousConnection.isEmpty())
111                     && (developerConnection == null || developerConnection.isEmpty())
112                     && StringUtils.isEmpty(scm.getUrl())) {
113                 result = false;
114             }
115         }
116 
117         return result;
118     }
119 
120     @Override
121     public void executeReport(Locale locale) {
122         ScmRenderer r = new ScmRenderer(
123                 getLog(),
124                 scmManager,
125                 getSink(),
126                 getProject().getModel(),
127                 getI18N(locale),
128                 locale,
129                 checkoutDirectoryName,
130                 webAccessUrl,
131                 anonymousConnection,
132                 developerConnection,
133                 scmTag);
134 
135         r.render();
136     }
137 
138     /** {@inheritDoc} */
139     public String getOutputName() {
140         return "scm";
141     }
142 
143     @Override
144     protected String getI18Nsection() {
145         return "scm";
146     }
147 
148     // ----------------------------------------------------------------------
149     // Private
150     // ----------------------------------------------------------------------
151 
152     /**
153      * Internal renderer class
154      */
155     private static class ScmRenderer extends AbstractProjectInfoRenderer {
156         private static final String LS = System.lineSeparator();
157 
158         private Log log;
159 
160         private Model model;
161 
162         private ScmManager scmManager;
163 
164         /**
165          * To support more SCM
166          */
167         private String anonymousConnection;
168 
169         private String devConnection;
170 
171         private String checkoutDirectoryName;
172 
173         private String webAccessUrl;
174 
175         private String scmTag;
176 
177         ScmRenderer(
178                 Log log,
179                 ScmManager scmManager,
180                 Sink sink,
181                 Model model,
182                 I18N i18n,
183                 Locale locale,
184                 String checkoutDirName,
185                 String webAccessUrl,
186                 String anonymousConnection,
187                 String devConnection,
188                 String scmTag) {
189             super(sink, i18n, locale);
190 
191             this.log = log;
192 
193             this.scmManager = scmManager;
194 
195             this.model = model;
196 
197             this.checkoutDirectoryName = checkoutDirName;
198 
199             this.webAccessUrl = webAccessUrl;
200 
201             this.anonymousConnection = anonymousConnection;
202 
203             this.devConnection = devConnection;
204 
205             this.scmTag = scmTag;
206         }
207 
208         @Override
209         protected String getI18Nsection() {
210             return "scm";
211         }
212 
213         @Override
214         protected void renderBody() {
215             Scm scm = model.getScm();
216             if (scm == null
217                     || (anonymousConnection == null || anonymousConnection.isEmpty())
218                             && (devConnection == null || devConnection.isEmpty())
219                             && StringUtils.isEmpty(scm.getUrl())) {
220                 startSection(getTitle());
221 
222                 paragraph(getI18nString("noscm"));
223 
224                 endSection();
225 
226                 return;
227             }
228 
229             ScmRepository anonymousRepository = getScmRepository(anonymousConnection);
230             ScmRepository devRepository = getScmRepository(devConnection);
231 
232             // Overview section
233             renderOverviewSection(anonymousRepository, devRepository);
234 
235             // Web access section
236             renderWebAccessSection(webAccessUrl);
237 
238             // Anonymous access section if needed
239             renderAnonymousAccessSection(anonymousRepository);
240 
241             // Developer access section
242             renderDeveloperAccessSection(devRepository);
243 
244             // Access from behind a firewall section if needed
245             renderAccessBehindFirewallSection(devRepository);
246 
247             // Access through a proxy section if needed
248             renderAccessThroughProxySection(anonymousRepository, devRepository);
249         }
250 
251         /**
252          * Render the overview section
253          *
254          * @param anonymousRepository the anonymous repository
255          * @param devRepository the developer repository
256          */
257         private void renderOverviewSection(ScmRepository anonymousRepository, ScmRepository devRepository) {
258             startSection(getI18nString("overview.title"));
259 
260             if (isScmSystem(anonymousRepository, "git") || isScmSystem(devRepository, "git")) {
261                 sink.paragraph();
262                 linkPatternedText(getI18nString("git.intro"));
263                 sink.paragraph_();
264             } else if (isScmSystem(anonymousRepository, "hg") || isScmSystem(devRepository, "hg")) {
265                 sink.paragraph();
266                 linkPatternedText(getI18nString("hg.intro"));
267                 sink.paragraph_();
268             } else if (isScmSystem(anonymousRepository, "svn") || isScmSystem(devRepository, "svn")) {
269                 sink.paragraph();
270                 linkPatternedText(getI18nString("svn.intro"));
271                 sink.paragraph_();
272             } else {
273                 paragraph(getI18nString("general.intro"));
274             }
275 
276             endSection();
277         }
278 
279         /**
280          * Render the web access section
281          *
282          * @param scmUrl The URL to the project's browsable repository.
283          */
284         private void renderWebAccessSection(String scmUrl) {
285             startSection(getI18nString("webaccess.title"));
286 
287             if (scmUrl == null || scmUrl.isEmpty()) {
288                 paragraph(getI18nString("webaccess.nourl"));
289             } else {
290                 paragraph(getI18nString("webaccess.url"));
291 
292                 verbatimLink(scmUrl, scmUrl);
293             }
294 
295             endSection();
296         }
297 
298         /**
299          * Render the anonymous access section depending the repository.
300          * <p>
301          * Note: ClearCase, Starteam et Perforce seems to have no anonymous access.
302          * </p>
303          *
304          * @param anonymousRepository the anonymous repository
305          */
306         private void renderAnonymousAccessSection(ScmRepository anonymousRepository) {
307             if (anonymousConnection == null || anonymousConnection.isEmpty()) {
308                 return;
309             }
310 
311             startSection(getI18nString("anonymousaccess.title"));
312 
313             if (anonymousRepository != null && isScmSystem(anonymousRepository, "git")) {
314                 GitScmProviderRepository gitRepo =
315                         (GitScmProviderRepository) anonymousRepository.getProviderRepository();
316 
317                 anonymousAccessGit(gitRepo);
318             } else if (anonymousRepository != null && isScmSystem(anonymousRepository, "hg")) {
319                 HgScmProviderRepository hgRepo = (HgScmProviderRepository) anonymousRepository.getProviderRepository();
320 
321                 anonymousAccessMercurial(hgRepo);
322             } else if (anonymousRepository != null && isScmSystem(anonymousRepository, "svn")) {
323                 SvnScmProviderRepository svnRepo =
324                         (SvnScmProviderRepository) anonymousRepository.getProviderRepository();
325 
326                 anonymousAccessSubversion(svnRepo);
327             } else {
328                 paragraph(getI18nString("anonymousaccess.general.intro"));
329 
330                 verbatimText(anonymousConnection.substring(4));
331             }
332 
333             endSection();
334         }
335 
336         /**
337          * Render the developer access section
338          *
339          * @param devRepository the dev repository
340          */
341         private void renderDeveloperAccessSection(ScmRepository devRepository) {
342             if (devConnection == null || devConnection.isEmpty()) {
343                 return;
344             }
345 
346             startSection(getI18nString("devaccess.title"));
347 
348             if (devRepository != null && isScmSystem(devRepository, "git")) {
349                 GitScmProviderRepository gitRepo = (GitScmProviderRepository) devRepository.getProviderRepository();
350 
351                 developerAccessGit(gitRepo);
352             } else if (devRepository != null && isScmSystem(devRepository, "hg")) {
353                 HgScmProviderRepository hgRepo = (HgScmProviderRepository) devRepository.getProviderRepository();
354 
355                 developerAccessMercurial(hgRepo);
356             } else if (devRepository != null && isScmSystem(devRepository, "svn")) {
357                 SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) devRepository.getProviderRepository();
358 
359                 developerAccessSubversion(svnRepo);
360             } else {
361                 paragraph(getI18nString("devaccess.general.intro"));
362 
363                 verbatimText(devConnection.substring(4));
364             }
365 
366             endSection();
367         }
368 
369         /**
370          * Render the access from behind a firewall section
371          *
372          * @param devRepository the dev repository
373          */
374         private void renderAccessBehindFirewallSection(ScmRepository devRepository) {
375             startSection(getI18nString("accessbehindfirewall.title"));
376 
377             if (devRepository != null && isScmSystem(devRepository, "svn")) {
378                 SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) devRepository.getProviderRepository();
379 
380                 paragraph(getI18nString("accessbehindfirewall.svn.intro"));
381 
382                 verbatimText("$ svn checkout " + svnRepo.getUrl() + " " + checkoutDirectoryName);
383             } else {
384                 paragraph(getI18nString("accessbehindfirewall.general.intro"));
385             }
386 
387             endSection();
388         }
389 
390         /**
391          * Render the access from behind a firewall section
392          *
393          * @param anonymousRepository the anonymous repository
394          * @param devRepository the dev repository
395          */
396         private void renderAccessThroughProxySection(ScmRepository anonymousRepository, ScmRepository devRepository) {
397             if (isScmSystem(anonymousRepository, "svn") || isScmSystem(devRepository, "svn")) {
398                 startSection(getI18nString("accessthroughtproxy.title"));
399 
400                 paragraph(getI18nString("accessthroughtproxy.svn.intro1"));
401                 paragraph(getI18nString("accessthroughtproxy.svn.intro2"));
402                 paragraph(getI18nString("accessthroughtproxy.svn.intro3"));
403 
404                 verbatimText(
405                         "[global]" + LS + "http-proxy-host = your.proxy.name" + LS + "http-proxy-port = 3128" + LS);
406 
407                 endSection();
408             }
409         }
410 
411         // Git
412 
413         private void gitClone(String url) {
414             // in the future, git scm url should support both repository + path: at the moment, require a hack
415             // to remove path added to repository
416             int index = url.indexOf(".git/");
417             if (index > 0) {
418                 url = url.substring(0, index + 4);
419             }
420 
421             boolean head = (scmTag == null || scmTag.isEmpty()) || "HEAD".equals(scmTag);
422             verbatimText("$ git clone " + (head ? "" : ("--branch " + scmTag + ' ')) + url);
423         }
424 
425         /**
426          * Create the documentation to provide an anonymous access with a <code>Git</code> SCM. For example, generate
427          * the following command line:
428          * <p>
429          * git clone uri
430          * </p>
431          *
432          * @param gitRepo
433          */
434         private void anonymousAccessGit(GitScmProviderRepository gitRepo) {
435             sink.paragraph();
436             linkPatternedText(getI18nString("anonymousaccess.git.intro"));
437             sink.paragraph_();
438 
439             gitClone(gitRepo.getFetchUrl());
440         }
441 
442         // Mercurial
443 
444         /**
445          * Create the documentation to provide an anonymous access with a <code>Mercurial</code> SCM. For example,
446          * generate the following command line:
447          * <p>
448          * hg clone uri
449          * </p>
450          *
451          * @param hgRepo
452          */
453         private void anonymousAccessMercurial(HgScmProviderRepository hgRepo) {
454             sink.paragraph();
455             linkPatternedText(getI18nString("anonymousaccess.hg.intro"));
456             sink.paragraph_();
457 
458             verbatimText("$ hg clone " + hgRepo.getURI());
459         }
460 
461         // Git
462 
463         /**
464          * Create the documentation to provide an developer access with a <code>Git</code> SCM. For example, generate
465          * the following command line:
466          * <p>
467          * git clone repo
468          * </p>
469          *
470          * @param gitRepo
471          */
472         private void developerAccessGit(GitScmProviderRepository gitRepo) {
473             sink.paragraph();
474             linkPatternedText(getI18nString("devaccess.git.intro"));
475             sink.paragraph_();
476 
477             gitClone(gitRepo.getPushUrl());
478         }
479 
480         // Mercurial
481 
482         /**
483          * Create the documentation to provide an developer access with a <code>Mercurial</code> SCM. For example,
484          * generate the following command line:
485          * <p>
486          * hg clone repo
487          * </p>
488          *
489          * @param hgRepo
490          */
491         private void developerAccessMercurial(HgScmProviderRepository hgRepo) {
492             sink.paragraph();
493             linkPatternedText(getI18nString("devaccess.hg.intro"));
494             sink.paragraph_();
495 
496             verbatimText("$ hg clone " + hgRepo.getURI());
497         }
498 
499         // Subversion
500 
501         /**
502          * Create the documentation to provide an anonymous access with a <code>Subversion</code>
503          * SCM. For example, generate the following command line:
504          * <p>
505          * svn checkout http://svn.apache.org/repos/asf/maven/components/trunk maven
506          * </p>
507          *
508          * @param svnRepo
509          * @see <a href="http://svnbook.red-bean.com/">http://svnbook.red-bean.com/</a>
510          */
511         private void anonymousAccessSubversion(SvnScmProviderRepository svnRepo) {
512             paragraph(getI18nString("anonymousaccess.svn.intro"));
513 
514             verbatimText("$ svn checkout " + svnRepo.getUrl() + " " + checkoutDirectoryName);
515         }
516 
517         /**
518          * Create the documentation to provide an developer access with a <code>Subversion</code>
519          * SCM. For example, generate the following command line:
520          * <p>
521          * svn checkout https://svn.apache.org/repos/asf/maven/components/trunk maven
522          * </p>
523          * <p>
524          * svn commit --username your-username -m "A message"
525          * </p>
526          *
527          * @param svnRepo
528          * @see <a href="http://svnbook.red-bean.com/">http://svnbook.red-bean.com/</a>
529          */
530         private void developerAccessSubversion(SvnScmProviderRepository svnRepo) {
531             if (svnRepo.getUrl() != null) {
532                 if (svnRepo.getUrl().startsWith("https://")) {
533                     paragraph(getI18nString("devaccess.svn.intro1.https"));
534                 } else if (svnRepo.getUrl().startsWith("svn://")) {
535                     paragraph(getI18nString("devaccess.svn.intro1.svn"));
536                 } else if (svnRepo.getUrl().startsWith("svn+ssh://")) {
537                     paragraph(getI18nString("devaccess.svn.intro1.svnssh"));
538                 } else {
539                     paragraph(getI18nString("devaccess.svn.intro1.other"));
540                 }
541             }
542 
543             StringBuilder sb = new StringBuilder();
544 
545             sb.append("$ svn checkout ").append(svnRepo.getUrl()).append(" ").append(checkoutDirectoryName);
546 
547             verbatimText(sb.toString());
548 
549             paragraph(getI18nString("devaccess.svn.intro2"));
550 
551             sb = new StringBuilder();
552             sb.append("$ svn commit --username your-username -m \"A message\"");
553 
554             verbatimText(sb.toString());
555         }
556 
557         /**
558          * Return a <code>SCM repository</code> defined by a given url
559          *
560          * @param scmUrl an SCM URL
561          * @return a valid SCM repository or null
562          */
563         public ScmRepository getScmRepository(String scmUrl) {
564             if (scmUrl == null || scmUrl.isEmpty()) {
565                 return null;
566             }
567 
568             ScmRepository repo = null;
569             List<String> messages = new ArrayList<>();
570             try {
571                 messages.addAll(scmManager.validateScmRepository(scmUrl));
572             } catch (Exception e) {
573                 messages.add(e.getMessage());
574             }
575 
576             if (!messages.isEmpty()) {
577                 StringBuilder sb = new StringBuilder();
578                 boolean isIntroAdded = false;
579                 for (String msg : messages) {
580                     // Ignore NoSuchScmProviderException msg
581                     // See impl of AbstractScmManager#validateScmRepository()
582                     if (msg.startsWith("No such provider")) {
583                         continue;
584                     }
585 
586                     if (!isIntroAdded) {
587                         sb.append("This SCM url '");
588                         sb.append(scmUrl);
589                         sb.append("' is invalid due to the following errors:");
590                         sb.append(LS);
591                         isIntroAdded = true;
592                     }
593                     sb.append(" * ");
594                     sb.append(msg);
595                     sb.append(LS);
596                 }
597 
598                 if (StringUtils.isNotEmpty(sb.toString())) {
599                     sb.append("For more information about SCM URL Format, please refer to: "
600                             + "http://maven.apache.org/scm/scm-url-format.html");
601 
602                     throw new IllegalArgumentException(sb.toString());
603                 }
604             }
605 
606             try {
607                 repo = scmManager.makeScmRepository(scmUrl);
608             } catch (Exception e) {
609                 // Should be already catched
610                 if (log.isDebugEnabled()) {
611                     log.debug(e.getMessage(), e);
612                 }
613             }
614 
615             return repo;
616         }
617 
618         /**
619          * Convenience method that return true is the defined <code>SCM repository</code> is a known provider.
620          * <p>
621          * Currently, we fully support ClearCase, CVS, Git, Perforce, Mercurial, Starteam and Subversion
622          * by the maven-scm-providers component.
623          * </p>
624          *
625          * @param scmRepository a SCM repository
626          * @param scmProvider a SCM provider name
627          * @return true if the provider of the given SCM repository is equal to the given scm provider.
628          * @see <a href="http://svn.apache.org/repos/asf/maven/scm/trunk/maven-scm-providers/">maven-scm-providers</a>
629          */
630         private static boolean isScmSystem(ScmRepository scmRepository, String scmProvider) {
631             if (scmProvider == null || scmProvider.isEmpty()) {
632                 return false;
633             }
634             return scmRepository != null && scmProvider.equalsIgnoreCase(scmRepository.getProvider());
635         }
636     }
637 }