001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.scm.manager;
020
021import java.io.File;
022import java.util.ArrayList;
023import java.util.Date;
024import java.util.List;
025import java.util.Map;
026import java.util.Optional;
027import java.util.concurrent.ConcurrentHashMap;
028
029import org.apache.maven.scm.CommandParameters;
030import org.apache.maven.scm.ScmBranch;
031import org.apache.maven.scm.ScmBranchParameters;
032import org.apache.maven.scm.ScmException;
033import org.apache.maven.scm.ScmFileSet;
034import org.apache.maven.scm.ScmTagParameters;
035import org.apache.maven.scm.ScmVersion;
036import org.apache.maven.scm.command.add.AddScmResult;
037import org.apache.maven.scm.command.blame.BlameScmRequest;
038import org.apache.maven.scm.command.blame.BlameScmResult;
039import org.apache.maven.scm.command.branch.BranchScmResult;
040import org.apache.maven.scm.command.changelog.ChangeLogScmRequest;
041import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
042import org.apache.maven.scm.command.checkin.CheckInScmResult;
043import org.apache.maven.scm.command.checkout.CheckOutScmResult;
044import org.apache.maven.scm.command.diff.DiffScmResult;
045import org.apache.maven.scm.command.edit.EditScmResult;
046import org.apache.maven.scm.command.export.ExportScmResult;
047import org.apache.maven.scm.command.list.ListScmResult;
048import org.apache.maven.scm.command.mkdir.MkdirScmResult;
049import org.apache.maven.scm.command.remove.RemoveScmResult;
050import org.apache.maven.scm.command.status.StatusScmResult;
051import org.apache.maven.scm.command.tag.TagScmResult;
052import org.apache.maven.scm.command.unedit.UnEditScmResult;
053import org.apache.maven.scm.command.update.UpdateScmResult;
054import org.apache.maven.scm.provider.ScmProvider;
055import org.apache.maven.scm.provider.ScmProviderRepository;
056import org.apache.maven.scm.provider.ScmUrlUtils;
057import org.apache.maven.scm.repository.ScmRepository;
058import org.apache.maven.scm.repository.ScmRepositoryException;
059import org.apache.maven.scm.repository.UnknownRepositoryStructure;
060import org.slf4j.Logger;
061import org.slf4j.LoggerFactory;
062
063import static java.util.Objects.requireNonNull;
064
065/**
066 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
067 * @author <a href="mailto:brett@apache.org">Brett Porter</a>
068 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
069 */
070public abstract class AbstractScmManager implements ScmManager {
071    protected final Logger logger = LoggerFactory.getLogger(getClass());
072
073    private final Map<String, ScmProvider> scmProviders = new ConcurrentHashMap<>();
074
075    private final Map<String, String> userProviderTypes = new ConcurrentHashMap<>();
076
077    protected void setScmProviders(Map<String, ScmProvider> providers) {
078        requireNonNull(providers);
079        this.scmProviders.clear();
080        // first provider must not be overwritten by subsequent ones if they are registered for the same key
081        providers.forEach(this.scmProviders::putIfAbsent);
082    }
083
084    /**
085     * @param providerType the type of SCM, e.g. <code>svn</code>, <code>git</code>
086     * @param provider     the provider that will be used for that SCM type
087     * @deprecated use {@link #setScmProvider(String, ScmProvider)} instead
088     */
089    @Deprecated
090    protected void addScmProvider(String providerType, ScmProvider provider) {
091        setScmProvider(providerType, provider);
092    }
093
094    /**
095     * Set a provider to be used for a type of SCM.
096     * If there was already a designed provider for that type it will be replaced.
097     *
098     * @param providerType the type of SCM, e.g. <code>svn</code>, <code>git</code>
099     * @param provider     the provider that will be used for that SCM type
100     */
101    @Override
102    public void setScmProvider(String providerType, ScmProvider provider) {
103        requireNonNull(providerType);
104        requireNonNull(provider);
105        scmProviders.put(providerType, provider);
106    }
107
108    // ----------------------------------------------------------------------
109    // ScmManager Implementation
110    // ----------------------------------------------------------------------
111
112    /**
113     * {@inheritDoc}
114     */
115    @Override
116    public ScmProvider getProviderByUrl(String scmUrl) throws ScmRepositoryException, NoSuchScmProviderException {
117        requireNonNull(scmUrl, "The scm url cannot be null.");
118
119        String providerType = ScmUrlUtils.getProvider(scmUrl);
120
121        return getProviderByType(providerType);
122    }
123
124    /**
125     * {@inheritDoc}
126     */
127    @Override
128    public void setScmProviderImplementation(String providerType, String providerImplementation) {
129        requireNonNull(providerType);
130        requireNonNull(providerImplementation);
131        userProviderTypes.put(providerType, providerImplementation);
132    }
133
134    /**
135     * {@inheritDoc}
136     */
137    @Override
138    public ScmProvider getProviderByType(String providerType) throws NoSuchScmProviderException {
139        String usedProviderType = System.getProperty("maven.scm.provider." + providerType + ".implementation");
140
141        if (usedProviderType == null) {
142            usedProviderType = userProviderTypes.getOrDefault(providerType, providerType);
143        }
144
145        ScmProvider scmProvider = scmProviders.get(usedProviderType);
146
147        if (scmProvider == null) {
148            throw new NoSuchScmProviderException(usedProviderType);
149        }
150
151        return scmProvider;
152    }
153
154    /**
155     * {@inheritDoc}
156     */
157    @Override
158    public ScmProvider getProviderByRepository(ScmRepository repository) throws NoSuchScmProviderException {
159        return getProviderByType(repository.getProvider());
160    }
161
162    // ----------------------------------------------------------------------
163    // Repository
164    // ----------------------------------------------------------------------
165
166    /**
167     * {@inheritDoc}
168     */
169    @Override
170    public ScmRepository makeScmRepository(String scmUrl) throws ScmRepositoryException, NoSuchScmProviderException {
171        requireNonNull(scmUrl, "The scm url cannot be null.");
172
173        char delimiter = ScmUrlUtils.getDelimiter(scmUrl).charAt(0);
174
175        String providerType = ScmUrlUtils.getProvider(scmUrl);
176
177        ScmProvider provider = getProviderByType(providerType);
178
179        String scmSpecificUrl = cleanScmUrl(scmUrl.substring(providerType.length() + 5));
180
181        ScmProviderRepository providerRepository = provider.makeProviderScmRepository(scmSpecificUrl, delimiter);
182
183        return new ScmRepository(providerType, providerRepository);
184    }
185
186    /**
187     * Clean the SCM url by removing all ../ in path.
188     *
189     * @param scmUrl the SCM url
190     * @return the cleaned SCM url
191     */
192    protected String cleanScmUrl(String scmUrl) {
193        requireNonNull(scmUrl, "The scm url cannot be null.");
194
195        String pathSeparator = "";
196
197        int indexOfDoubleDot = -1;
198
199        // Clean Unix path
200        if (scmUrl.indexOf("../") > 1) {
201            pathSeparator = "/";
202
203            indexOfDoubleDot = scmUrl.indexOf("../");
204        }
205
206        // Clean windows path
207        if (scmUrl.indexOf("..\\") > 1) {
208            pathSeparator = "\\";
209
210            indexOfDoubleDot = scmUrl.indexOf("..\\");
211        }
212
213        if (indexOfDoubleDot > 1) {
214            int startOfTextToRemove = scmUrl.substring(0, indexOfDoubleDot - 1).lastIndexOf(pathSeparator);
215
216            String beginUrl = "";
217            if (startOfTextToRemove >= 0) {
218                beginUrl = scmUrl.substring(0, startOfTextToRemove);
219            }
220
221            String endUrl = scmUrl.substring(indexOfDoubleDot + 3);
222
223            scmUrl = beginUrl + pathSeparator + endUrl;
224
225            // Check if we have other double dot
226            if (scmUrl.indexOf("../") > 1 || scmUrl.indexOf("..\\") > 1) {
227                scmUrl = cleanScmUrl(scmUrl);
228            }
229        }
230
231        return scmUrl;
232    }
233
234    /**
235     * {@inheritDoc}
236     */
237    @Override
238    public ScmRepository makeProviderScmRepository(String providerType, File path)
239            throws ScmRepositoryException, UnknownRepositoryStructure, NoSuchScmProviderException {
240        requireNonNull(providerType, "The provider type cannot be null.");
241
242        ScmProvider provider = getProviderByType(providerType);
243
244        ScmProviderRepository providerRepository = provider.makeProviderScmRepository(path);
245
246        return new ScmRepository(providerType, providerRepository);
247    }
248
249    @Override
250    public Optional<ScmRepository> makeProviderScmRepository(File workingDirectory) {
251        //
252        for (ScmProvider provider : scmProviders.values()) {
253            logger.debug(
254                    "Checking if SCM provider {} is suitable for processing: {}",
255                    provider.getScmType(),
256                    workingDirectory);
257            try {
258                ScmProviderRepository providerRepository = provider.makeProviderScmRepository(workingDirectory);
259                return Optional.of(new ScmRepository(provider.getScmType(), providerRepository));
260            } catch (ScmRepositoryException | UnknownRepositoryStructure e) {
261                logger.debug(
262                        "SCM provider {} is not suitable for processing: {}",
263                        provider.getScmType(),
264                        workingDirectory,
265                        e);
266            }
267        }
268        return Optional.empty();
269    }
270
271    /**
272     * {@inheritDoc}
273     */
274    @Override
275    public List<String> validateScmRepository(String scmUrl) {
276        List<String> messages = new ArrayList<>(ScmUrlUtils.validate(scmUrl));
277
278        String providerType = ScmUrlUtils.getProvider(scmUrl);
279
280        ScmProvider provider;
281
282        try {
283            provider = getProviderByType(providerType);
284        } catch (NoSuchScmProviderException e) {
285            messages.add("No such provider installed '" + providerType + "'.");
286
287            return messages;
288        }
289
290        String scmSpecificUrl = cleanScmUrl(scmUrl.substring(providerType.length() + 5));
291
292        List<String> providerMessages = provider.validateScmUrl(
293                scmSpecificUrl, ScmUrlUtils.getDelimiter(scmUrl).charAt(0));
294
295        requireNonNull(providerMessages, "The SCM provider cannot return null from validateScmUrl().");
296
297        messages.addAll(providerMessages);
298
299        return messages;
300    }
301
302    /**
303     * {@inheritDoc}
304     */
305    @Override
306    public AddScmResult add(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
307        return this.getProviderByRepository(repository).add(repository, fileSet);
308    }
309
310    /**
311     * {@inheritDoc}
312     */
313    @Override
314    public AddScmResult add(ScmRepository repository, ScmFileSet fileSet, String message) throws ScmException {
315        return this.getProviderByRepository(repository).add(repository, fileSet, message);
316    }
317
318    /**
319     * {@inheritDoc}
320     */
321    @Override
322    public BranchScmResult branch(ScmRepository repository, ScmFileSet fileSet, String branchName) throws ScmException {
323        ScmBranchParameters scmBranchParameters = new ScmBranchParameters("");
324        return this.getProviderByRepository(repository).branch(repository, fileSet, branchName, scmBranchParameters);
325    }
326
327    /**
328     * {@inheritDoc}
329     */
330    @Override
331    public BranchScmResult branch(ScmRepository repository, ScmFileSet fileSet, String branchName, String message)
332            throws ScmException {
333        ScmBranchParameters scmBranchParameters = new ScmBranchParameters(message);
334        return this.getProviderByRepository(repository).branch(repository, fileSet, branchName, scmBranchParameters);
335    }
336
337    /**
338     * {@inheritDoc}
339     */
340    @Override
341    public ChangeLogScmResult changeLog(
342            ScmRepository repository, ScmFileSet fileSet, Date startDate, Date endDate, int numDays, ScmBranch branch)
343            throws ScmException {
344        return this.getProviderByRepository(repository)
345                .changeLog(repository, fileSet, startDate, endDate, numDays, branch);
346    }
347
348    /**
349     * {@inheritDoc}
350     */
351    @Override
352    public ChangeLogScmResult changeLog(
353            ScmRepository repository,
354            ScmFileSet fileSet,
355            Date startDate,
356            Date endDate,
357            int numDays,
358            ScmBranch branch,
359            String datePattern)
360            throws ScmException {
361        return this.getProviderByRepository(repository)
362                .changeLog(repository, fileSet, startDate, endDate, numDays, branch, datePattern);
363    }
364
365    /**
366     * {@inheritDoc}
367     */
368    @Override
369    public ChangeLogScmResult changeLog(ChangeLogScmRequest scmRequest) throws ScmException {
370        return this.getProviderByRepository(scmRequest.getScmRepository()).changeLog(scmRequest);
371    }
372
373    /**
374     * {@inheritDoc}
375     */
376    @Override
377    public ChangeLogScmResult changeLog(
378            ScmRepository repository, ScmFileSet fileSet, ScmVersion startVersion, ScmVersion endVersion)
379            throws ScmException {
380        return this.getProviderByRepository(repository).changeLog(repository, fileSet, startVersion, endVersion);
381    }
382
383    /**
384     * {@inheritDoc}
385     */
386    @Override
387    public ChangeLogScmResult changeLog(
388            ScmRepository repository,
389            ScmFileSet fileSet,
390            ScmVersion startRevision,
391            ScmVersion endRevision,
392            String datePattern)
393            throws ScmException {
394        return this.getProviderByRepository(repository)
395                .changeLog(repository, fileSet, startRevision, endRevision, datePattern);
396    }
397
398    /**
399     * {@inheritDoc}
400     */
401    @Override
402    public CheckInScmResult checkIn(ScmRepository repository, ScmFileSet fileSet, String message) throws ScmException {
403        return this.getProviderByRepository(repository).checkIn(repository, fileSet, message);
404    }
405
406    /**
407     * {@inheritDoc}
408     */
409    @Override
410    public CheckInScmResult checkIn(ScmRepository repository, ScmFileSet fileSet, ScmVersion revision, String message)
411            throws ScmException {
412        return this.getProviderByRepository(repository).checkIn(repository, fileSet, revision, message);
413    }
414
415    @Override
416    public CheckInScmResult checkIn(ScmRepository repository, ScmFileSet fileSet, CommandParameters commandParameters)
417            throws ScmException {
418        return this.getProviderByRepository(repository).checkIn(repository, fileSet, commandParameters);
419    }
420
421    /**
422     * {@inheritDoc}
423     */
424    @Override
425    public CheckOutScmResult checkOut(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
426        return this.getProviderByRepository(repository).checkOut(repository, fileSet);
427    }
428
429    /**
430     * {@inheritDoc}
431     */
432    @Override
433    public CheckOutScmResult checkOut(ScmRepository repository, ScmFileSet fileSet, ScmVersion version)
434            throws ScmException {
435        return this.getProviderByRepository(repository).checkOut(repository, fileSet, version);
436    }
437
438    /**
439     * {@inheritDoc}
440     */
441    @Override
442    public CheckOutScmResult checkOut(ScmRepository repository, ScmFileSet fileSet, boolean recursive)
443            throws ScmException {
444        return this.getProviderByRepository(repository).checkOut(repository, fileSet, recursive);
445    }
446
447    /**
448     * {@inheritDoc}
449     */
450    @Override
451    public CheckOutScmResult checkOut(
452            ScmRepository repository, ScmFileSet fileSet, ScmVersion version, boolean recursive) throws ScmException {
453        return this.getProviderByRepository(repository).checkOut(repository, fileSet, version, recursive);
454    }
455
456    /**
457     * {@inheritDoc}
458     */
459    @Override
460    public DiffScmResult diff(
461            ScmRepository repository, ScmFileSet fileSet, ScmVersion startVersion, ScmVersion endVersion)
462            throws ScmException {
463        return this.getProviderByRepository(repository).diff(repository, fileSet, startVersion, endVersion);
464    }
465
466    /**
467     * {@inheritDoc}
468     */
469    @Override
470    public EditScmResult edit(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
471        return this.getProviderByRepository(repository).edit(repository, fileSet);
472    }
473
474    /**
475     * {@inheritDoc}
476     */
477    @Override
478    public ExportScmResult export(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
479        return this.getProviderByRepository(repository).export(repository, fileSet);
480    }
481
482    /**
483     * {@inheritDoc}
484     */
485    @Override
486    public ExportScmResult export(ScmRepository repository, ScmFileSet fileSet, ScmVersion version)
487            throws ScmException {
488        return this.getProviderByRepository(repository).export(repository, fileSet, version);
489    }
490
491    /**
492     * {@inheritDoc}
493     */
494    @Override
495    public ExportScmResult export(ScmRepository repository, ScmFileSet fileSet, String outputDirectory)
496            throws ScmException {
497        return this.getProviderByRepository(repository).export(repository, fileSet, (ScmVersion) null, outputDirectory);
498    }
499
500    /**
501     * {@inheritDoc}
502     */
503    @Override
504    public ExportScmResult export(
505            ScmRepository repository, ScmFileSet fileSet, ScmVersion version, String outputDirectory)
506            throws ScmException {
507        return this.getProviderByRepository(repository).export(repository, fileSet, version, outputDirectory);
508    }
509
510    /**
511     * {@inheritDoc}
512     */
513    @Override
514    public ListScmResult list(ScmRepository repository, ScmFileSet fileSet, boolean recursive, ScmVersion version)
515            throws ScmException {
516        return this.getProviderByRepository(repository).list(repository, fileSet, recursive, version);
517    }
518
519    /**
520     * {@inheritDoc}
521     */
522    @Override
523    public MkdirScmResult mkdir(ScmRepository repository, ScmFileSet fileSet, String message, boolean createInLocal)
524            throws ScmException {
525        return this.getProviderByRepository(repository).mkdir(repository, fileSet, message, createInLocal);
526    }
527
528    /**
529     * {@inheritDoc}
530     */
531    @Override
532    public RemoveScmResult remove(ScmRepository repository, ScmFileSet fileSet, String message) throws ScmException {
533        return this.getProviderByRepository(repository).remove(repository, fileSet, message);
534    }
535
536    /**
537     * {@inheritDoc}
538     */
539    @Override
540    public StatusScmResult status(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
541        return this.getProviderByRepository(repository).status(repository, fileSet);
542    }
543
544    /**
545     * {@inheritDoc}
546     */
547    @Override
548    public TagScmResult tag(ScmRepository repository, ScmFileSet fileSet, String tagName) throws ScmException {
549        return this.tag(repository, fileSet, tagName, "");
550    }
551
552    /**
553     * {@inheritDoc}
554     */
555    @Override
556    public TagScmResult tag(ScmRepository repository, ScmFileSet fileSet, String tagName, String message)
557            throws ScmException {
558        ScmTagParameters scmTagParameters = new ScmTagParameters(message);
559        return this.getProviderByRepository(repository).tag(repository, fileSet, tagName, scmTagParameters);
560    }
561
562    /**
563     * {@inheritDoc}
564     */
565    @Override
566    public UnEditScmResult unedit(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
567        return this.getProviderByRepository(repository).unedit(repository, fileSet);
568    }
569
570    /**
571     * {@inheritDoc}
572     */
573    @Override
574    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet) throws ScmException {
575        return this.getProviderByRepository(repository).update(repository, fileSet);
576    }
577
578    /**
579     * {@inheritDoc}
580     */
581    @Override
582    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, ScmVersion version)
583            throws ScmException {
584        return this.getProviderByRepository(repository).update(repository, fileSet, version);
585    }
586
587    /**
588     * {@inheritDoc}
589     */
590    @Override
591    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, boolean runChangelog)
592            throws ScmException {
593        return this.getProviderByRepository(repository).update(repository, fileSet, runChangelog);
594    }
595
596    /**
597     * {@inheritDoc}
598     */
599    @Override
600    public UpdateScmResult update(
601            ScmRepository repository, ScmFileSet fileSet, ScmVersion version, boolean runChangelog)
602            throws ScmException {
603        return this.getProviderByRepository(repository).update(repository, fileSet, version, runChangelog);
604    }
605
606    /**
607     * {@inheritDoc}
608     */
609    @Override
610    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, String datePattern)
611            throws ScmException {
612        return this.getProviderByRepository(repository).update(repository, fileSet, (ScmVersion) null, datePattern);
613    }
614
615    /**
616     * {@inheritDoc}
617     */
618    @Override
619    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, ScmVersion version, String datePattern)
620            throws ScmException {
621        return this.getProviderByRepository(repository).update(repository, fileSet, version, datePattern);
622    }
623
624    /**
625     * {@inheritDoc}
626     */
627    @Override
628    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, Date lastUpdate) throws ScmException {
629        return this.getProviderByRepository(repository).update(repository, fileSet, (ScmVersion) null, lastUpdate);
630    }
631
632    /**
633     * {@inheritDoc}
634     */
635    @Override
636    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, ScmVersion version, Date lastUpdate)
637            throws ScmException {
638        return this.getProviderByRepository(repository).update(repository, fileSet, version, lastUpdate);
639    }
640
641    /**
642     * {@inheritDoc}
643     */
644    @Override
645    public UpdateScmResult update(ScmRepository repository, ScmFileSet fileSet, Date lastUpdate, String datePattern)
646            throws ScmException {
647        return this.getProviderByRepository(repository)
648                .update(repository, fileSet, (ScmVersion) null, lastUpdate, datePattern);
649    }
650
651    /**
652     * {@inheritDoc}
653     */
654    @Override
655    public UpdateScmResult update(
656            ScmRepository repository, ScmFileSet fileSet, ScmVersion version, Date lastUpdate, String datePattern)
657            throws ScmException {
658        return this.getProviderByRepository(repository).update(repository, fileSet, version, lastUpdate, datePattern);
659    }
660
661    /**
662     * {@inheritDoc}
663     */
664    @Override
665    public BlameScmResult blame(ScmRepository repository, ScmFileSet fileSet, String filename) throws ScmException {
666        return this.getProviderByRepository(repository).blame(repository, fileSet, filename);
667    }
668
669    @Override
670    public BlameScmResult blame(BlameScmRequest blameScmRequest) throws ScmException {
671        return this.getProviderByRepository(blameScmRequest.getScmRepository()).blame(blameScmRequest);
672    }
673}