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.shared.release.phase;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.File;
26  import java.util.Arrays;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.apache.maven.project.MavenProject;
34  import org.apache.maven.scm.ScmException;
35  import org.apache.maven.scm.ScmFile;
36  import org.apache.maven.scm.ScmFileSet;
37  import org.apache.maven.scm.command.status.StatusScmResult;
38  import org.apache.maven.scm.manager.NoSuchScmProviderException;
39  import org.apache.maven.scm.provider.ScmProvider;
40  import org.apache.maven.scm.repository.ScmRepository;
41  import org.apache.maven.scm.repository.ScmRepositoryException;
42  import org.apache.maven.shared.release.ReleaseExecutionException;
43  import org.apache.maven.shared.release.ReleaseFailureException;
44  import org.apache.maven.shared.release.ReleaseResult;
45  import org.apache.maven.shared.release.config.ReleaseDescriptor;
46  import org.apache.maven.shared.release.env.ReleaseEnvironment;
47  import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
48  import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
49  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
50  import org.apache.maven.shared.release.scm.ScmTranslator;
51  import org.codehaus.plexus.util.SelectorUtils;
52  import org.codehaus.plexus.util.StringUtils;
53  
54  import static java.util.Objects.requireNonNull;
55  
56  /**
57   * See if there are any local modifications to the files before proceeding with SCM operations and the release.
58   *
59   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
60   */
61  @Singleton
62  @Named("scm-check-modifications")
63  public class ScmCheckModificationsPhase extends AbstractReleasePhase {
64      /**
65       * Tool that gets a configured SCM repository from release configuration.
66       */
67      private final ScmRepositoryConfigurator scmRepositoryConfigurator;
68  
69      /**
70       * SCM URL translators mapped by provider name.
71       */
72      private final Map<String, ScmTranslator> scmTranslators;
73  
74      /**
75       * The filepatterns to exclude from the status check.
76       *
77       * @todo proper construction of filenames, especially release properties
78       */
79      private final Set<String> exclusionPatterns = new HashSet<>(Arrays.asList(
80              "**" + File.separator + "pom.xml.backup", "**" + File.separator + "pom.xml.tag",
81              "**" + File.separator + "pom.xml.next", "**" + File.separator + "pom.xml.branch",
82              "**" + File.separator + "release.properties", "**" + File.separator + "pom.xml.releaseBackup"));
83  
84      @Inject
85      public ScmCheckModificationsPhase(
86              ScmRepositoryConfigurator scmRepositoryConfigurator, Map<String, ScmTranslator> scmTranslators) {
87          this.scmRepositoryConfigurator = requireNonNull(scmRepositoryConfigurator);
88          this.scmTranslators = requireNonNull(scmTranslators);
89      }
90  
91      @Override
92      public ReleaseResult execute(
93              ReleaseDescriptor releaseDescriptor,
94              ReleaseEnvironment releaseEnvironment,
95              List<MavenProject> reactorProjects)
96              throws ReleaseExecutionException, ReleaseFailureException {
97          ReleaseResult relResult = new ReleaseResult();
98  
99          List<String> additionalExcludes = releaseDescriptor.getCheckModificationExcludes();
100 
101         if (additionalExcludes != null) {
102             // SelectorUtils expects OS-specific paths and patterns
103             for (String additionalExclude : additionalExcludes) {
104                 exclusionPatterns.add(
105                         additionalExclude.replace("\\", File.separator).replace("/", File.separator));
106             }
107         }
108 
109         logInfo(relResult, "Verifying that there are no local modifications...");
110         logInfo(relResult, "  ignoring changes on: " + StringUtils.join(exclusionPatterns.toArray(), ", "));
111 
112         ScmRepository repository;
113         ScmProvider provider;
114         try {
115             repository = scmRepositoryConfigurator.getConfiguredRepository(
116                     releaseDescriptor, releaseEnvironment.getSettings());
117 
118             provider = scmRepositoryConfigurator.getRepositoryProvider(repository);
119         } catch (ScmRepositoryException e) {
120             throw new ReleaseScmRepositoryException(
121                     e.getMessage() + " for URL: " + releaseDescriptor.getScmSourceUrl(), e.getValidationMessages());
122         } catch (NoSuchScmProviderException e) {
123             throw new ReleaseExecutionException("Unable to configure SCM repository: " + e.getMessage(), e);
124         }
125 
126         StatusScmResult result;
127         try {
128             result = provider.status(repository, new ScmFileSet(new File(releaseDescriptor.getWorkingDirectory())));
129         } catch (ScmException e) {
130             throw new ReleaseExecutionException(
131                     "An error occurred during the status check process: " + e.getMessage(), e);
132         }
133 
134         if (!result.isSuccess()) {
135             throw new ReleaseScmCommandException("Unable to check for local modifications", result);
136         }
137 
138         List<ScmFile> changedFiles = result.getChangedFiles();
139 
140         if (!changedFiles.isEmpty()) {
141             ScmTranslator scmTranslator = scmTranslators.get(repository.getProvider());
142 
143             // TODO: would be nice for SCM status command to do this for me.
144             for (Iterator<ScmFile> i = changedFiles.iterator(); i.hasNext(); ) {
145                 ScmFile f = i.next();
146 
147                 String path;
148                 if (scmTranslator != null) {
149                     path = scmTranslator.toRelativePath(f.getPath());
150                 } else {
151                     path = f.getPath();
152                 }
153 
154                 // SelectorUtils expects File.separator, don't standardize!
155                 String fileName = path.replace("\\", File.separator).replace("/", File.separator);
156 
157                 for (String exclusionPattern : exclusionPatterns) {
158                     if (SelectorUtils.matchPath(exclusionPattern, fileName)) {
159                         logDebug(relResult, "Ignoring changed file: " + fileName);
160                         i.remove();
161                         break;
162                     }
163                 }
164             }
165         }
166 
167         if (!changedFiles.isEmpty()) {
168             StringBuilder message = new StringBuilder();
169 
170             for (ScmFile file : changedFiles) {
171                 message.append(file.toString());
172                 message.append("\n");
173             }
174 
175             throw new ReleaseFailureException(
176                     "Cannot prepare the release because you have local modifications : \n" + message);
177         }
178 
179         relResult.setResultCode(ReleaseResult.SUCCESS);
180 
181         return relResult;
182     }
183 
184     @Override
185     public ReleaseResult simulate(
186             ReleaseDescriptor releaseDescriptor,
187             ReleaseEnvironment releaseEnvironment,
188             List<MavenProject> reactorProjects)
189             throws ReleaseExecutionException, ReleaseFailureException {
190         // It makes no modifications, so simulate is the same as execute
191         return execute(releaseDescriptor, releaseEnvironment, reactorProjects);
192     }
193 }