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