001package org.apache.maven.scm.provider.svn; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.maven.scm.ScmBranch; 023import org.apache.maven.scm.ScmTag; 024import org.apache.maven.scm.ScmVersion; 025import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository; 026import org.codehaus.plexus.util.StringUtils; 027 028/** 029 * 030 */ 031public final class SvnTagBranchUtils 032{ 033 034 private SvnTagBranchUtils() 035 { 036 } 037 038 public static final String[] REVISION_SPECIFIERS = new String[]{"HEAD", "BASE", "COMMITTED", "PREV"}; 039 040 public static final String SVN_TRUNK = "trunk"; 041 042 public static final String SVN_BRANCHES = "branches"; 043 044 public static final String SVN_TAGS = "tags"; 045 046 public static final String[] SVN_BASE_DIRS = new String[]{SVN_TRUNK, SVN_BRANCHES, SVN_TAGS}; 047 048 /** 049 * Simple helper function to concatenate two paths together with a "/". 050 * Handles trailing / on basePath. 051 * Returns no trailing "/" if the addlPath is null 052 */ 053 static String appendPath( String basePath, String addlPath ) 054 { 055 basePath = StringUtils.stripEnd( basePath, "/" ); 056 057 if ( StringUtils.isEmpty( addlPath ) ) 058 { 059 return basePath; 060 } 061 else 062 { 063 return basePath + "/" + StringUtils.stripStart( addlPath, "/" ); 064 } 065 } 066 067 /** 068 * Returns the project root for the given repository url, 069 * where "project root" is the root of the /trunk, /branches, /tags 070 * directories 071 * 072 * @param repoPath Repository path/url to be searched 073 * @return 074 */ 075 public static String getProjectRoot( String repoPath ) 076 { 077 for ( int i = 0; i < SVN_BASE_DIRS.length; i++ ) 078 { 079 String base = "/" + SVN_BASE_DIRS[i]; 080 int pos = repoPath.lastIndexOf( base + "/" ); 081 if ( repoPath.endsWith( base ) ) 082 { 083 return repoPath.substring( 0, repoPath.length() - base.length() ); 084 } 085 else if ( pos >= 0 ) 086 { 087 return repoPath.substring( 0, pos ); 088 } 089 } 090 091 // At this point we were unable to locate the project root of this url 092 // so assume that the repository url specified is the project root 093 return appendPath( repoPath, null ); 094 } 095 096 public static String resolveTagBase( SvnScmProviderRepository repository ) 097 { 098 return resolveTagBase( repository.getUrl() ); 099 } 100 101 public static String resolveTagBase( String repositoryUrl ) 102 { 103 return appendPath( getProjectRoot( repositoryUrl ), SVN_TAGS ); 104 } 105 106 public static String resolveBranchBase( SvnScmProviderRepository repository ) 107 { 108 return resolveBranchBase( repository.getUrl() ); 109 } 110 111 public static String resolveBranchBase( String repositoryUrl ) 112 { 113 return appendPath( getProjectRoot( repositoryUrl ), SVN_BRANCHES ); 114 } 115 116 /** 117 * Resolves a tag to a repository url. 118 * By supplying the repository to this function (rather than calling {@link #resolveTagUrl(String,ScmTag)} 119 * the resolution can use the repository's tagBase to override the default tag location. 120 * 121 * @param repository the repository to use as a base for tag resolution 122 * @param tag tag name 123 * @return 124 * @see #resolveUrl(String,String,String,ScmBranch) 125 */ 126 public static String resolveTagUrl( SvnScmProviderRepository repository, ScmTag tag ) 127 { 128 return resolveUrl( repository.getUrl(), repository.getTagBase(), SVN_TAGS, tag ); 129 } 130 131 /** 132 * Resolves a tag to a repository url. 133 * Will not use the {@link SvnScmProviderRepository#getTagBase()} during resolution. 134 * 135 * @param repositoryUrl string url for the repository 136 * @param tag tag name 137 * @return 138 * @see #resolveUrl(String,String,String,ScmBranch) 139 */ 140 public static String resolveTagUrl( String repositoryUrl, ScmTag tag ) 141 { 142 return resolveUrl( repositoryUrl, null, SVN_TAGS, tag ); 143 } 144 145 /** 146 * Resolves a branch name to a repository url. 147 * By supplying the repository to this function (rather than calling {@link #resolveBranchUrl(String,ScmBranch)} 148 * the resolution can use the repository's tagBase to override the default tag location. 149 * 150 * @param repository the repository to use as a base for tag resolution 151 * @param branch tag name 152 * @return 153 * @see #resolveUrl(String,String,String,ScmBranch) 154 */ 155 public static String resolveBranchUrl( SvnScmProviderRepository repository, ScmBranch branch ) 156 { 157 return resolveUrl( repository.getUrl(), repository.getBranchBase(), SVN_BRANCHES, branch ); 158 } 159 160 /** 161 * Resolves a branch name to a repository url. 162 * Will not use the {@link SvnScmProviderRepository#getTagBase()} during resolution. 163 * 164 * @param repositoryUrl string url for the repository 165 * @param branch branch name 166 * @return 167 * @see #resolveUrl(String,String,String,ScmBranch) 168 */ 169 public static String resolveBranchUrl( String repositoryUrl, ScmBranch branch ) 170 { 171 return resolveUrl( repositoryUrl, resolveBranchBase( repositoryUrl ), SVN_BRANCHES, branch ); 172 } 173 174 private static String addSuffix( String baseString, String suffix ) 175 { 176 return ( suffix != null ) ? baseString + suffix : baseString; 177 } 178 179 180 /** 181 * Resolves a tag or branch name to a repository url.<br> 182 * If the <code>branchTagName</code> is an absolute URL, that value is returned. 183 * (i.e. http://foo.com/svn/myproject/tags/my-tag)<br> 184 * <p/> 185 * If the repository has a {@link SvnScmProviderRepository#getTagBase()} specified, 186 * the tag is simply appended to the tagBase value. Note that at this time, we are using 187 * the tagBase as a base for both branches and tags.<br> 188 * <p/> 189 * If the <code>branchTagName</code> contains a branch/tag specifier (i.e. "/branches", "/tags", "/trunk"), 190 * the <code>branchTagName</code> is appended to the <code>projectRoot</code> without adding the subdir.<br> 191 * Else, the result is in the format of <code>projectRoot/subdir/branchTagName</code> directory.<br> 192 * 193 * @param repositoryUrl string url for the repository 194 * @param tagBase tagBase to use. 195 * @param subdir Subdirectory to append to the project root 196 * (for branching use "branches", tags use "tags") 197 * @param branchTag Name of the actual branch or tag. Can be an absolute url, simple tag/branch name, 198 * or even contain a relative path to the root like "branches/my-branch" 199 * @return 200 */ 201 public static String resolveUrl( String repositoryUrl, String tagBase, String subdir, ScmBranch branchTag ) 202 { 203 String branchTagName = branchTag.getName(); 204 String projectRoot = getProjectRoot( repositoryUrl ); 205 branchTagName = StringUtils.strip( branchTagName, "/" ); 206 207 if ( StringUtils.isEmpty( branchTagName ) ) 208 { 209 return null; 210 } 211 212 // Look for a query string as in ViewCVS urls 213 String queryString = null; 214 if ( repositoryUrl.indexOf( '?' ) >= 0 ) 215 { 216 queryString = repositoryUrl.substring( repositoryUrl.indexOf( '?' ) ); 217 // if repositoryUrl contains a query string, remove it from repositoryUrlRoot; will be re-appended later 218 projectRoot = StringUtils.replace( projectRoot, queryString, "" ); 219 } 220 221 if ( branchTagName.indexOf( "://" ) >= 0 ) 222 { 223 // branch/tag is already an absolute url so just return it. 224 return branchTagName; 225 } 226 227 // User has a tagBase specified so just return the name appended to the tagBase 228 if ( StringUtils.isNotEmpty( tagBase ) && !tagBase.equals( resolveTagBase( repositoryUrl ) ) 229 && !tagBase.equals( resolveBranchBase( repositoryUrl ) ) ) 230 { 231 return appendPath( tagBase, branchTagName ); 232 } 233 234 // Look for any "branches/" or "tags/" specifiers in the branchTagName. If one occurs, 235 // don't append the subdir to the projectRoot when appending the name 236 for ( int i = 0; i < SVN_BASE_DIRS.length; i++ ) 237 { 238 if ( branchTagName.startsWith( SVN_BASE_DIRS[i] + "/" ) ) 239 { 240 return addSuffix( appendPath( projectRoot, branchTagName ), queryString ); 241 } 242 } 243 244 return addSuffix( appendPath( appendPath( projectRoot, subdir ), branchTagName ), queryString ); 245 } 246 247 /* Helper function that does the checking for {@link #isRevisionSpecifier} 248 */ 249 private static boolean checkRevisionArg( String arg ) 250 { 251 if ( StringUtils.isNumeric( arg ) || ( arg.startsWith( "{" ) && arg.endsWith( "}" ) ) ) 252 { 253 return true; 254 } 255 256 for ( int i = 0; i < REVISION_SPECIFIERS.length; i++ ) 257 { 258 if ( REVISION_SPECIFIERS[i].equalsIgnoreCase( arg ) ) 259 { 260 return true; 261 } 262 } 263 264 return false; 265 } 266 267 /** 268 * Returns whether the supplied tag refers to an actual revision or 269 * is specifying a tag/branch url in the repository. 270 * According to the subversion documentation, the following are valid revision specifiers: 271 * NUMBER revision number 272 * "{" DATE "}" revision at start of the date 273 * "HEAD" latest in repository 274 * "BASE" base rev of item's working copy 275 * "COMMITTED" last commit at or before BASE 276 * "PREV" 277 * <p/> 278 * For command such as diff, the revision argument can be in the format of: 279 * IDENTIFIER:IDENTIFIER where IDENTIFIER is one of the args listed above 280 */ 281 public static boolean isRevisionSpecifier( ScmVersion version ) 282 { 283 if ( version == null ) 284 { 285 return false; 286 } 287 288 String versionName = version.getName(); 289 290 if ( StringUtils.isEmpty( versionName ) ) 291 { 292 return false; 293 } 294 295 if ( checkRevisionArg( versionName ) ) 296 { 297 return true; 298 } 299 300 String[] parts = StringUtils.split( versionName, ":" ); 301 if ( parts.length == 2 && StringUtils.isNotEmpty( parts[0] ) && StringUtils.isNotEmpty( parts[1] ) ) 302 { 303 return checkRevisionArg( parts[0] ) && checkRevisionArg( parts[1] ); 304 } 305 306 return false; 307 } 308}