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