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.scm.provider.svn;
20  
21  import org.apache.commons.lang3.StringUtils;
22  import org.apache.maven.scm.ScmBranch;
23  import org.apache.maven.scm.ScmTag;
24  import org.apache.maven.scm.ScmVersion;
25  import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
26  
27  public final class SvnTagBranchUtils {
28  
29      private SvnTagBranchUtils() {}
30  
31      public static final String[] REVISION_SPECIFIERS = new String[] {"HEAD", "BASE", "COMMITTED", "PREV"};
32  
33      public static final String SVN_TRUNK = "trunk";
34  
35      public static final String SVN_BRANCHES = "branches";
36  
37      public static final String SVN_TAGS = "tags";
38  
39      public static final String[] SVN_BASE_DIRS = new String[] {SVN_TRUNK, SVN_BRANCHES, SVN_TAGS};
40  
41      /**
42       * Simple helper function to concatenate two paths together with a "/".
43       * Handles trailing / on basePath.
44       * Returns no trailing "/" if the addlPath is null.
45       */
46      static String appendPath(String basePath, String addlPath) {
47          basePath = StringUtils.stripEnd(basePath, "/");
48  
49          if (addlPath == null || addlPath.isEmpty()) {
50              return basePath;
51          } else {
52              return basePath + "/" + StringUtils.stripStart(addlPath, "/");
53          }
54      }
55  
56      /**
57       * Returns the project root for the given repository url,
58       * where "project root" is the root of the /trunk, /branches, /tags
59       * directories.
60       *
61       * @param repoPath repository path/url to be searched
62       * @return TODO
63       */
64      public static String getProjectRoot(String repoPath) {
65          for (int i = 0; i < SVN_BASE_DIRS.length; i++) {
66              String base = "/" + SVN_BASE_DIRS[i];
67              int pos = repoPath.lastIndexOf(base + "/");
68              if (repoPath.endsWith(base)) {
69                  return repoPath.substring(0, repoPath.length() - base.length());
70              } else if (pos >= 0) {
71                  return repoPath.substring(0, pos);
72              }
73          }
74  
75          // At this point we were unable to locate the project root of this url
76          // so assume that the repository url specified is the project root
77          return appendPath(repoPath, null);
78      }
79  
80      public static String resolveTagBase(SvnScmProviderRepository repository) {
81          return resolveTagBase(repository.getUrl());
82      }
83  
84      public static String resolveTagBase(String repositoryUrl) {
85          return appendPath(getProjectRoot(repositoryUrl), SVN_TAGS);
86      }
87  
88      public static String resolveBranchBase(SvnScmProviderRepository repository) {
89          return resolveBranchBase(repository.getUrl());
90      }
91  
92      public static String resolveBranchBase(String repositoryUrl) {
93          return appendPath(getProjectRoot(repositoryUrl), SVN_BRANCHES);
94      }
95  
96      /**
97       * Resolves a tag to a repository url.
98       * By supplying the repository to this function (rather than calling {@link #resolveTagUrl(String,ScmTag)}
99       * the resolution can use the repository's tagBase to override the default tag location.
100      *
101      * @param repository the repository to use as a base for tag resolution
102      * @param tag        tag name
103      * @return TODO
104      * @see #resolveUrl(String,String,String,ScmBranch)
105      */
106     public static String resolveTagUrl(SvnScmProviderRepository repository, ScmTag tag) {
107         return resolveUrl(repository.getUrl(), repository.getTagBase(), SVN_TAGS, tag);
108     }
109 
110     /**
111      * Resolves a tag to a repository url.
112      * Will not use the {@link SvnScmProviderRepository#getTagBase()} during resolution.
113      *
114      * @param repositoryUrl string url for the repository
115      * @param tag           tag name
116      * @return TODO
117      * @see #resolveUrl(String,String,String,ScmBranch)
118      */
119     public static String resolveTagUrl(String repositoryUrl, ScmTag tag) {
120         return resolveUrl(repositoryUrl, null, SVN_TAGS, tag);
121     }
122 
123     /**
124      * Resolves a branch name to a repository url.
125      * By supplying the repository to this function (rather than calling {@link #resolveBranchUrl(String,ScmBranch)}
126      * the resolution can use the repository's tagBase to override the default tag location.
127      *
128      * @param repository the repository to use as a base for tag resolution
129      * @param branch     tag name
130      * @return TODO
131      * @see #resolveUrl(String,String,String,ScmBranch)
132      */
133     public static String resolveBranchUrl(SvnScmProviderRepository repository, ScmBranch branch) {
134         return resolveUrl(repository.getUrl(), repository.getBranchBase(), SVN_BRANCHES, branch);
135     }
136 
137     /**
138      * Resolves a branch name to a repository url.
139      * Will not use the {@link SvnScmProviderRepository#getTagBase()} during resolution.
140      *
141      * @param repositoryUrl string url for the repository
142      * @param branch        branch name
143      * @return TODO
144      * @see #resolveUrl(String,String,String,ScmBranch)
145      */
146     public static String resolveBranchUrl(String repositoryUrl, ScmBranch branch) {
147         return resolveUrl(repositoryUrl, resolveBranchBase(repositoryUrl), SVN_BRANCHES, branch);
148     }
149 
150     private static String addSuffix(String baseString, String suffix) {
151         return (suffix != null) ? baseString + suffix : baseString;
152     }
153 
154     /**
155      * Resolves a tag or branch name to a repository url.<br>
156      * If the <code>branchTagName</code> is an absolute URL, that value is returned.
157      * (i.e. http://foo.com/svn/myproject/tags/my-tag)<br>
158      * <p>
159      * If the repository has a {@link SvnScmProviderRepository#getTagBase()} specified,
160      * the tag is simply appended to the tagBase value. Note that at this time, we are using
161      * the tagBase as a base for both branches and tags.<br>
162      * <p>
163      * If the <code>branchTagName</code> contains a branch/tag specifier (i.e. "/branches", "/tags", "/trunk"),
164      * the <code>branchTagName</code> is appended to the <code>projectRoot</code> without adding the subdir.<br>
165      * Else, the result is in the format of <code>projectRoot/subdir/branchTagName</code> directory.<br>
166      *
167      * @param repositoryUrl string url for the repository
168      * @param tagBase       tagBase to use
169      * @param subdir        subdirectory to append to the project root
170      *                      (for branching use "branches", tags use "tags")
171      * @param branchTag     name of the actual branch or tag. Can be an absolute url, simple tag/branch name,
172      *                      or even contain a relative path to the root like "branches/my-branch"
173      * @return TODO
174      */
175     public static String resolveUrl(String repositoryUrl, String tagBase, String subdir, ScmBranch branchTag) {
176         String branchTagName = branchTag.getName();
177         String projectRoot = getProjectRoot(repositoryUrl);
178         branchTagName = StringUtils.strip(branchTagName, "/");
179 
180         if (branchTagName == null || branchTagName.isEmpty()) {
181             return null;
182         }
183 
184         // Look for a query string as in ViewSVN urls
185         String queryString = null;
186         if (repositoryUrl.indexOf('?') >= 0) {
187             queryString = repositoryUrl.substring(repositoryUrl.indexOf('?'));
188             // if repositoryUrl contains a query string, remove it from repositoryUrlRoot; will be re-appended later
189             projectRoot = StringUtils.replace(projectRoot, queryString, "");
190         }
191 
192         if (branchTagName.indexOf("://") >= 0) {
193             // branch/tag is already an absolute url so just return it.
194             return branchTagName;
195         }
196 
197         // User has a tagBase specified so just return the name appended to the tagBase
198         if ((tagBase != null && !tagBase.isEmpty())
199                 && !tagBase.equals(resolveTagBase(repositoryUrl))
200                 && !tagBase.equals(resolveBranchBase(repositoryUrl))) {
201             return appendPath(tagBase, branchTagName);
202         }
203 
204         // Look for any "branches/" or "tags/" specifiers in the branchTagName. If one occurs,
205         // don't append the subdir to the projectRoot when appending the name
206         for (int i = 0; i < SVN_BASE_DIRS.length; i++) {
207             if (branchTagName.startsWith(SVN_BASE_DIRS[i] + "/")) {
208                 return addSuffix(appendPath(projectRoot, branchTagName), queryString);
209             }
210         }
211 
212         return addSuffix(appendPath(appendPath(projectRoot, subdir), branchTagName), queryString);
213     }
214 
215     /* Helper function that does the checking for {@link #isRevisionSpecifier}
216      */
217     private static boolean checkRevisionArg(String arg) {
218         if (StringUtils.isNumeric(arg) || (arg.startsWith("{") && arg.endsWith("}"))) {
219             return true;
220         }
221 
222         for (int i = 0; i < REVISION_SPECIFIERS.length; i++) {
223             if (REVISION_SPECIFIERS[i].equalsIgnoreCase(arg)) {
224                 return true;
225             }
226         }
227 
228         return false;
229     }
230 
231     /**
232      * Returns whether the supplied tag refers to an actual revision or
233      * is specifying a tag/branch url in the repository.
234      * According to the subversion documentation, the following are valid revision specifiers:
235      * NUMBER       revision number
236      * "{" DATE "}" revision at start of the date
237      * "HEAD"       latest in repository
238      * "BASE"       base rev of item's working copy
239      * "COMMITTED"  last commit at or before BASE
240      * "PREV"
241      * <p>
242      * For command such as diff, the revision argument can be in the format of:
243      * IDENTIFIER:IDENTIFIER   where IDENTIFIER is one of the args listed above.
244      */
245     public static boolean isRevisionSpecifier(ScmVersion version) {
246         if (version == null) {
247             return false;
248         }
249 
250         String versionName = version.getName();
251 
252         if (versionName == null || versionName.isEmpty()) {
253             return false;
254         }
255 
256         if (checkRevisionArg(versionName)) {
257             return true;
258         }
259 
260         String[] parts = StringUtils.split(versionName, ":");
261         if (parts.length == 2 && StringUtils.isNotEmpty(parts[0]) && StringUtils.isNotEmpty(parts[1])) {
262             return checkRevisionArg(parts[0]) && checkRevisionArg(parts[1]);
263         }
264 
265         return false;
266     }
267 }