merge with branch issue-47

This commit is contained in:
Sebastian Sdorra
2011-11-26 12:39:54 +01:00
37 changed files with 1560 additions and 274 deletions

View File

@@ -59,6 +59,20 @@ public class ScmClientConfig
this.dateFormat = dateFormat;
}
/**
* Constructs {@link ScmClientConfig} object
*
* @since 1.9
*
* @param dateFormat
* @param disableGroupingGrid true to disable repository grouping
*/
public ScmClientConfig(String dateFormat, boolean disableGroupingGrid)
{
this.dateFormat = dateFormat;
this.disableGroupingGrid = disableGroupingGrid;
}
//~--- get methods ----------------------------------------------------------
/**
@@ -74,6 +88,18 @@ public class ScmClientConfig
return dateFormat;
}
/**
* Returns true if the grouping of repositories is disabled.
*
* @since 1.9
*
* @return true if the grouping of repositories is disabled
*/
public boolean isDisableGroupingGrid()
{
return disableGroupingGrid;
}
//~--- set methods ----------------------------------------------------------
/**
@@ -88,8 +114,24 @@ public class ScmClientConfig
this.dateFormat = dateFormat;
}
/**
* Enables or disables the grouping of repositories.
*
* @since 1.9
*
*
* @param disableGroupingGrid
*/
public void setDisableGroupingGrid(boolean disableGroupingGrid)
{
this.disableGroupingGrid = disableGroupingGrid;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private String dateFormat;
/** Field description */
private boolean disableGroupingGrid = true;
}

View File

@@ -164,6 +164,7 @@ public class ScmConfiguration
this.proxyPassword = other.proxyPassword;
this.forceBaseUrl = other.forceBaseUrl;
this.baseUrl = other.baseUrl;
this.disableGroupingGrid = other.disableGroupingGrid;
// deprecated fields
this.sslPort = other.sslPort;
@@ -345,6 +346,17 @@ public class ScmConfiguration
return anonymousAccessEnabled;
}
/**
* Method description
*
* @since 1.9
* @return
*/
public boolean isDisableGroupingGrid()
{
return disableGroupingGrid;
}
/**
* Returns true if port forwarding is enabled.
*
@@ -451,6 +463,18 @@ public class ScmConfiguration
this.dateFormat = dateFormat;
}
/**
* Method description
*
* @since 1.9
*
* @param disableGroupingGrid
*/
public void setDisableGroupingGrid(boolean disableGroupingGrid)
{
this.disableGroupingGrid = disableGroupingGrid;
}
/**
* Method description
*
@@ -658,6 +682,9 @@ public class ScmConfiguration
private Set<ConfigChangedListener> listeners =
new HashSet<ConfigChangedListener>();
/** Field description */
private boolean disableGroupingGrid = false;
/**
* JavaScript date format, see http://jacwright.com/projects/javascript/date_format
*/

View File

@@ -64,6 +64,9 @@ public abstract class AbstractSimpleRepositoryHandler<C extends SimpleRepository
/** Field description */
public static final String DIRECTORY_REPOSITORY = "repositories";
/** Field description */
public static final String DOT = ".";
/** the logger for AbstractSimpleRepositoryHandler */
private static final Logger logger =
LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class);
@@ -106,6 +109,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends SimpleRepository
throw new RepositoryAllreadyExistExeption();
}
checkPath(directory);
fileSystem.create(directory);
create(repository, directory);
postCreate(repository, directory);
@@ -219,8 +223,18 @@ public abstract class AbstractSimpleRepositoryHandler<C extends SimpleRepository
if (isConfigured())
{
directory = new File(config.getRepositoryDirectory(),
repository.getName());
File repositoryDirectory = config.getRepositoryDirectory();
directory = new File(repositoryDirectory, repository.getName());
if (!IOUtil.isChild(repositoryDirectory, directory))
{
StringBuilder msg = new StringBuilder(directory.getPath());
msg.append("is not a child of ").append(repositoryDirectory.getPath());
throw new ConfigurationException(msg.toString());
}
}
else
{
@@ -298,6 +312,58 @@ public abstract class AbstractSimpleRepositoryHandler<C extends SimpleRepository
protected void postCreate(Repository repository, File directory)
throws IOException, RepositoryException {}
//~--- get methods ----------------------------------------------------------
/**
* Returns true if the directory is a repository.
*
*
* @param directory directory to check
*
* @return true if the directory is a repository
* @since 1.9
*/
protected boolean isRepository(File directory)
{
return new File(directory, DOT.concat(getType().getName())).exists();
}
//~--- methods --------------------------------------------------------------
/**
* Check path for existing repositories
*
*
* @param directory repository target directory
*
* @throws RepositoryAllreadyExistExeption
*/
private void checkPath(File directory) throws RepositoryAllreadyExistExeption
{
File repositoryDirectory = config.getRepositoryDirectory();
File parent = directory.getParentFile();
while ((parent != null) &&!repositoryDirectory.equals(parent))
{
if (logger.isTraceEnabled())
{
logger.trace("check {} for existing repository", parent);
}
if (isRepository(parent))
{
if (logger.isErrorEnabled())
{
logger.error("parent path {} is a repository", parent);
}
throw new RepositoryAllreadyExistExeption();
}
parent = parent.getParentFile();
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */

View File

@@ -434,7 +434,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject
/**
* Returns true if the {@link Repository} is valid.
* <ul>
* <li>The name is not empty and contains only A-z, 0-9, _, -</li>
* <li>The name is not empty and contains only A-z, 0-9, _, -, /</li>
* <li>The type is not empty</li>
* <li>The contact is empty or contains a valid email address</li>
* </ul>
@@ -445,7 +445,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject
@Override
public boolean isValid()
{
return ValidationUtil.isNameValid(name) && Util.isNotEmpty(type)
return ValidationUtil.isRepositoryNameValid(name) && Util.isNotEmpty(type)
&& ((Util.isEmpty(contact))
|| ValidationUtil.isMailAddressValid(contact));
}

View File

@@ -43,6 +43,8 @@ import sonia.scm.TypeManager;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
/**
* The central class for managing {@link Repository} objects.
* This class is a singleton and is available via injection.
@@ -80,6 +82,41 @@ public interface RepositoryManager
*/
public Collection<Type> getConfiguredTypes();
/**
* Returns the {@link Repository} associated to the request uri.
*
*
* @param request the current http request
*
* @return associated to the request uri
* @since 1.9
*/
public Repository getFromRequest(HttpServletRequest request);
/**
* Returns the {@link Repository} associated to the given type and path.
*
*
* @param type type of the repository (hg, git ...)
* @param uri
*
* @return the {@link Repository} associated to the given type and path
* @since 1.9
*/
public Repository getFromTypeAndUri(String type, String uri);
/**
* Returns the {@link Repository} associated to the request uri.
*
*
*
* @param uri request uri without context path
*
* @return associated to the request uri
* @since 1.9
*/
public Repository getFromUri(String uri);
/**
* Returns a {@link RepositoryHandler} by the given type (hg, git, svn ...).
*

View File

@@ -592,6 +592,30 @@ public class IOUtil
return cmds;
}
/**
* Method description
*
*
* @param name
*
* @return
* @since 1.9
*/
public static String trimSeperatorChars(String name)
{
if (name.startsWith(File.separator))
{
name = name.substring(1);
}
if (name.endsWith(File.separator))
{
name = name.substring(0, name.length() - 1);
}
return name;
}
//~--- get methods ----------------------------------------------------------
/**
@@ -664,6 +688,29 @@ public class IOUtil
return getScript(baseFile, baseFile.getAbsolutePath());
}
/**
* Method description
*
*
* @param parent
* @param child
* @since 1.9
*
* @return
*
*/
public static boolean isChild(File parent, File child)
{
try
{
return child.getCanonicalPath().startsWith(parent.getCanonicalPath());
}
catch (IOException ex)
{
throw new RuntimeException(ex);
}
}
//~--- methods --------------------------------------------------------------
/**

View File

@@ -51,6 +51,9 @@ public class ValidationUtil
/** Field description */
private static final String REGEX_NAME = "^[A-z0-9\\.\\-_]+$";
/** Field description */
private static final String REGEX_REPOSITORYNAME = "^[A-z0-9\\.\\-_/]+$";
/** Field description */
private static final String REGEX_USERNAME = "^[^ ][A-z0-9\\.\\-_@ ]+[^ ]$";
@@ -125,6 +128,20 @@ public class ValidationUtil
return result;
}
/**
* Method description
*
*
* @param name
* @since 1.9
*
* @return
*/
public static boolean isRepositoryNameValid(String name)
{
return Util.isNotEmpty(name) && name.matches(REGEX_REPOSITORYNAME);
}
/**
* Method description
*

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.web.filter;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Provider;
import sonia.scm.repository.Repository;
import sonia.scm.web.security.WebSecurityContext;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.http.HttpServletRequest;
/**
*
* @author Sebastian Sdorra
* @since 1.9
*/
public abstract class ProviderPermissionFilter extends PermissionFilter
{
/**
* Constructs ...
*
*
* @param securityContextProvider
* @param repositoryProvider
*/
public ProviderPermissionFilter(
Provider<WebSecurityContext> securityContextProvider,
Provider<Repository> repositoryProvider)
{
super(securityContextProvider);
this.repositoryProvider = repositoryProvider;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param request
*
* @return
*/
@Override
protected Repository getRepository(HttpServletRequest request)
{
return repositoryProvider.get();
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private Provider<Repository> repositoryProvider;
}

View File

@@ -62,6 +62,9 @@ public class GitRepositoryHandler
extends AbstractSimpleRepositoryHandler<GitConfig>
{
/** Field description */
public static final String DIRECTORY_REFS = "refs";
/** Field description */
public static final String TYPE_DISPLAYNAME = "Git";
@@ -267,4 +270,18 @@ public class GitRepositoryHandler
{
return GitConfig.class;
}
/**
* Method description
*
*
* @param directory
*
* @return
*/
@Override
protected boolean isRepository(File directory)
{
return new File(directory, DIRECTORY_REFS).exists();
}
}

View File

@@ -39,9 +39,8 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.filter.RegexPermissionFilter;
import sonia.scm.repository.Repository;
import sonia.scm.web.filter.ProviderPermissionFilter;
import sonia.scm.web.security.WebSecurityContext;
//~--- JDK imports ------------------------------------------------------------
@@ -53,7 +52,7 @@ import javax.servlet.http.HttpServletRequest;
* @author Sebastian Sdorra
*/
@Singleton
public class GitPermissionFilter extends RegexPermissionFilter
public class GitPermissionFilter extends ProviderPermissionFilter
{
/** Field description */
@@ -76,30 +75,18 @@ public class GitPermissionFilter extends RegexPermissionFilter
*
*
* @param securityContextProvider
* @param repositoryManager
* @param repositoryProvider
*/
@Inject
public GitPermissionFilter(
Provider<WebSecurityContext> securityContextProvider,
RepositoryManager repositoryManager)
Provider<Repository> repositoryProvider)
{
super(securityContextProvider, repositoryManager);
super(securityContextProvider, repositoryProvider);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
protected String getType()
{
return GitRepositoryHandler.TYPE_NAME;
}
/**
* Method description
*

View File

@@ -86,56 +86,6 @@ public class GitRepositoryResolver
this.handler = handler;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param name
*
* @return
*/
private static boolean isUnreasonableName(final String name)
{
if (name.length() == 0)
{
return true; // no empty paths
}
if (name.indexOf('\\') >= 0)
{
return true; // no windows/dos style paths
}
if (new File(name).isAbsolute())
{
return true; // no absolute paths
}
if (name.startsWith("../"))
{
return true; // no "l../etc/passwd"
}
if (name.contains("/../"))
{
return true; // no "foo/../etc/passwd"
}
if (name.contains("/./"))
{
return true; // "foo/./foo" is insane to ask
}
if (name.contains("//"))
{
return true; // double slashes is sloppy, don't use it
}
return false; // is a reasonable name
}
//~--- methods --------------------------------------------------------------
/**
@@ -158,11 +108,6 @@ public class GitRepositoryResolver
{
Repository repository = null;
if (isUnreasonableName(repositoryName))
{
throw new RepositoryNotFoundException(repositoryName);
}
try
{
GitConfig config = handler.getConfig();

View File

@@ -36,21 +36,20 @@ package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.http.server.GitServlet;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.resolver.RepositoryResolver;
import sonia.scm.repository.GitUtil;
import sonia.scm.util.HttpUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -70,10 +69,6 @@ public class ScmGitServlet extends GitServlet
/** Field description */
private static final long serialVersionUID = -7712897339207470674L;
/** Field description */
private static final Pattern REGEX_REPOSITORYNAME =
Pattern.compile("/git/([^/]+)/?.*");
//~--- constructors ---------------------------------------------------------
/**
@@ -83,12 +78,16 @@ public class ScmGitServlet extends GitServlet
*
* @param repositoryResolver
* @param receivePackFactory
* @param repositoryProvider
*/
@Inject
public ScmGitServlet(GitRepositoryResolver repositoryResolver,
GitReceivePackFactory receivePackFactory)
public ScmGitServlet(
GitRepositoryResolver repositoryResolver,
GitReceivePackFactory receivePackFactory,
Provider<sonia.scm.repository.Repository> repositoryProvider)
{
this.resolver = repositoryResolver;
this.repositoryProvider = repositoryProvider;
setRepositoryResolver(repositoryResolver);
setReceivePackFactory(receivePackFactory);
}
@@ -137,43 +136,38 @@ public class ScmGitServlet extends GitServlet
HttpServletResponse response)
throws ServletException, IOException
{
String uri = HttpUtil.getStrippedURI(request);
Matcher m = REGEX_REPOSITORYNAME.matcher(uri);
String name = null;
Repository repository = null;
sonia.scm.repository.Repository scmRepository = repositoryProvider.get();
try
if (scmRepository != null)
{
if (m.matches())
{
name = m.group(1);
repository = resolver.open(request, name);
}
Repository repository = null;
if (repository != null)
try
{
new GitRepositoryViewer().handleRequest(response, repository, name);
repository = resolver.open(request, scmRepository.getName());
new GitRepositoryViewer().handleRequest(response, repository,
scmRepository.getName());
}
else
catch (Exception ex)
{
response.sendError(HttpServletResponse.SC_NOT_FOUND);
throw new ServletException(ex);
}
finally
{
GitUtil.close(repository);
}
}
catch (Exception ex)
else
{
throw new ServletException(ex);
}
finally
{
if (repository != null)
{
repository.close();
}
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private Provider<sonia.scm.repository.Repository> repositoryProvider;
/** Field description */
private RepositoryResolver<HttpServletRequest> resolver;
}

View File

@@ -77,7 +77,6 @@ public class UnixHgInstaller extends AbstractHgInstaller
super.install(baseDirectory, config);
// search mercurial (hg)
if (Util.isEmpty(config.getHgBinary()))
{
String hg = IOUtil.search(COMMAND_HG);
@@ -100,9 +99,8 @@ public class UnixHgInstaller extends AbstractHgInstaller
}
}
}
// search python
// search python
if (Util.isEmpty(config.getPythonBinary()))
{
config.setPythonBinary(IOUtil.search(COMMAND_PYTHON));

View File

@@ -272,8 +272,9 @@ public class WindowsHgInstaller extends AbstractHgInstaller
IOUtil.copy(templateDirectory, new File(libDir, FILE_TEMPLATES));
}
File hg = new File( hgDirectory, FILE_MERCURIAL_EXE );
if ( hg.exists() )
File hg = new File(hgDirectory, FILE_MERCURIAL_EXE);
if (hg.exists())
{
config.setHgBinary(hg.getAbsolutePath());
}
@@ -322,8 +323,8 @@ public class WindowsHgInstaller extends AbstractHgInstaller
private File getMercurialDirectory(String hgBinary)
{
File directory = null;
if ( Util.isNotEmpty(hgBinary) )
if (Util.isNotEmpty(hgBinary))
{
File hg = new File(hgBinary);
@@ -332,8 +333,8 @@ public class WindowsHgInstaller extends AbstractHgInstaller
directory = hg.getParentFile();
}
}
if ( directory == null )
if (directory == null)
{
directory = getMercurialDirectoryFromRegistry();
}

View File

@@ -36,6 +36,7 @@ package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import sonia.scm.config.ScmConfiguration;
@@ -55,7 +56,6 @@ import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
@@ -102,19 +102,19 @@ public class HgCGIServlet extends HttpServlet
*
* @param cgiExecutorFactory
* @param configuration
* @param repositoryManager
* @param repositoryProvider
* @param handler
* @param hookManager
*/
@Inject
public HgCGIServlet(CGIExecutorFactory cgiExecutorFactory,
ScmConfiguration configuration,
RepositoryManager repositoryManager,
Provider<Repository> repositoryProvider,
HgRepositoryHandler handler, HgHookManager hookManager)
{
this.cgiExecutorFactory = cgiExecutorFactory;
this.configuration = configuration;
this.repositoryManager = repositoryManager;
this.repositoryProvider = repositoryProvider;
this.handler = handler;
this.hookManager = hookManager;
}
@@ -149,7 +149,7 @@ public class HgCGIServlet extends HttpServlet
HttpServletResponse response)
throws ServletException, IOException
{
Repository repository = getRepository(request);
Repository repository = repositoryProvider.get();
if (repository == null)
{
@@ -244,46 +244,6 @@ public class HgCGIServlet extends HttpServlet
return python;
}
/**
* Method description
*
*
* @param request
*
* @return
*/
private Repository getRepository(HttpServletRequest request)
{
Repository repository = null;
String uri = request.getRequestURI();
uri = uri.substring(request.getContextPath().length());
Matcher m = PATTERN_REPOSITORYNAME.matcher(uri);
if (m.matches())
{
String repositoryname = m.group(1);
repository = getRepository(repositoryname);
}
return repository;
}
/**
* Method description
*
*
* @param repositoryname
*
* @return
*/
private Repository getRepository(String repositoryname)
{
return repositoryManager.get(HgRepositoryHandler.TYPE_NAME, repositoryname);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@@ -302,5 +262,5 @@ public class HgCGIServlet extends HttpServlet
private HgHookManager hookManager;
/** Field description */
private RepositoryManager repositoryManager;
private Provider<Repository> repositoryProvider;
}

View File

@@ -51,6 +51,7 @@ import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.security.CipherUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
import sonia.scm.web.security.WebSecurityContext;
@@ -80,6 +81,9 @@ public class HgHookCallbackServlet extends HttpServlet
/** Field description */
public static final String HGHOOK_PRE_RECEIVE = "pretxnchangegroup";
/** Field description */
public static final String PARAM_REPOSITORYPATH = "repositoryPath";
/** Field description */
private static final String PARAM_CHALLENGE = "challenge";
@@ -91,7 +95,7 @@ public class HgHookCallbackServlet extends HttpServlet
/** Field description */
private static final Pattern REGEX_URL =
Pattern.compile("^/hook/hg/([^/]+)/([^/]+)$");
Pattern.compile("^/hook/hg/([^/]+)$");
/** the logger for HgHookCallbackServlet */
private static final Logger logger =
@@ -144,8 +148,8 @@ public class HgHookCallbackServlet extends HttpServlet
if (m.matches())
{
String repositoryId = m.group(1);
String type = m.group(2);
String repositoryId = getRepositoryName(request);
String type = m.group(1);
String challenge = request.getParameter(PARAM_CHALLENGE);
if (Util.isNotEmpty(challenge))
@@ -316,6 +320,43 @@ public class HgHookCallbackServlet extends HttpServlet
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param request
*
* @return
*/
private String getRepositoryName(HttpServletRequest request)
{
String name = null;
String path = request.getParameter(PARAM_REPOSITORYPATH);
if (Util.isNotEmpty(path))
{
int directoryLength =
handler.getConfig().getRepositoryDirectory().getAbsolutePath().length();
if (directoryLength < path.length())
{
name = IOUtil.trimSeperatorChars(path.substring(directoryLength));
}
else if (logger.isWarnEnabled())
{
logger.warn("path is shorter as the main hg repository path");
}
}
else if (logger.isWarnEnabled())
{
logger.warn("no repository path parameter found");
}
return name;
}
//~--- fields ---------------------------------------------------------------
/** Field description */

View File

@@ -39,9 +39,8 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.filter.RegexPermissionFilter;
import sonia.scm.repository.Repository;
import sonia.scm.web.filter.ProviderPermissionFilter;
import sonia.scm.web.security.WebSecurityContext;
//~--- JDK imports ------------------------------------------------------------
@@ -53,7 +52,7 @@ import javax.servlet.http.HttpServletRequest;
* @author Sebastian Sdorra
*/
@Singleton
public class HgPermissionFilter extends RegexPermissionFilter
public class HgPermissionFilter extends ProviderPermissionFilter
{
/**
@@ -61,30 +60,18 @@ public class HgPermissionFilter extends RegexPermissionFilter
*
*
* @param securityContextProvider
* @param repositoryManager
* @param repositoryProvider
*/
@Inject
public HgPermissionFilter(
Provider<WebSecurityContext> securityContextProvider,
RepositoryManager repositoryManager)
Provider<Repository> repositoryProvider)
{
super(securityContextProvider, repositoryManager);
super(securityContextProvider, repositoryProvider);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
protected String getType()
{
return HgRepositoryHandler.TYPE_NAME;
}
/**
* Method description
*

View File

@@ -48,9 +48,9 @@ def callback(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs)
failure = True
if node != None:
try:
url = baseUrl + os.path.basename(repo.root) + "/" + hooktype
url = baseUrl + hooktype
ui.debug( "send scm-hook to " + url + " and " + node + "\n" )
data = urllib.urlencode({'node': node, 'challenge': challenge, 'credentials': credentials})
data = urllib.urlencode({'node': node, 'challenge': challenge, 'credentials': credentials, 'repositoryPath': repo.root})
conn = urllib.urlopen(url, data);
if conn.code >= 200 and conn.code < 300:
ui.debug( "scm-hook " + hooktype + " success with status code " + str(conn.code) + "\n" )

View File

@@ -102,7 +102,7 @@ public class SvnRepositoryHandler
// register hook
if (repositoryManager != null)
{
FSHooks.registerHook(new SvnRepositoryHook(repositoryManager));
FSHooks.registerHook(new SvnRepositoryHook(repositoryManager, this));
}
else if (logger.isWarnEnabled())
{

View File

@@ -46,6 +46,7 @@ import org.tmatesoft.svn.core.internal.io.fs.FSHook;
import org.tmatesoft.svn.core.internal.io.fs.FSHookEvent;
import org.tmatesoft.svn.core.internal.io.fs.FSHooks;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
@@ -70,10 +71,13 @@ public class SvnRepositoryHook implements FSHook
*
*
* @param repositoryManager
* @param handler
*/
public SvnRepositoryHook(RepositoryManager repositoryManager)
public SvnRepositoryHook(RepositoryManager repositoryManager,
SvnRepositoryHandler handler)
{
this.repositoryManager = repositoryManager;
this.handler = handler;
}
//~--- methods --------------------------------------------------------------
@@ -151,8 +155,14 @@ public class SvnRepositoryHook implements FSHook
{
try
{
repositoryManager.fireHookEvent(SvnRepositoryHandler.TYPE_NAME,
directory.getName(), hookEvent);
String name =
directory.getAbsolutePath()
.substring(handler.getConfig().getRepositoryDirectory()
.getAbsolutePath().length());
name = IOUtil.trimSeperatorChars(name);
repositoryManager.fireHookEvent(SvnRepositoryHandler.TYPE_NAME, name,
hookEvent);
}
catch (Exception ex)
{
@@ -168,6 +178,9 @@ public class SvnRepositoryHook implements FSHook
//~--- fields ---------------------------------------------------------------
/** Field description */
private SvnRepositoryHandler handler;
/** Field description */
private RepositoryManager repositoryManager;
}

View File

@@ -35,10 +35,17 @@ package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Provider;
import org.tmatesoft.svn.core.internal.server.dav.DAVConfig;
import org.tmatesoft.svn.core.internal.server.dav.SVNPathBasedAccess;
import sonia.scm.repository.SvnConfig;
import sonia.scm.repository.Repository;
import sonia.scm.repository.SvnRepositoryHandler;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
/**
*
@@ -52,12 +59,15 @@ public class SvnDAVConfig extends DAVConfig
*
*
* @param davConfig
* @param config
* @param handler
* @param repositoryProvider
*/
public SvnDAVConfig(DAVConfig davConfig, SvnConfig config)
public SvnDAVConfig(DAVConfig davConfig, SvnRepositoryHandler handler,
Provider<Repository> repositoryProvider)
{
this.davConfig = davConfig;
this.config = config;
this.handler = handler;
this.repositoryProvider = repositoryProvider;
}
//~--- get methods ----------------------------------------------------------
@@ -71,7 +81,7 @@ public class SvnDAVConfig extends DAVConfig
@Override
public String getActivitiesDBPath()
{
return davConfig.getActivitiesDBPath();
return null;
}
/**
@@ -95,7 +105,19 @@ public class SvnDAVConfig extends DAVConfig
@Override
public String getRepositoryParentPath()
{
return config.getRepositoryDirectory().getAbsolutePath();
String path = null;
File directory = getRepositoryDirectory();
if (directory != null)
{
path = directory.getParent();
}
else
{
path = davConfig.getRepositoryPath();
}
return path;
}
/**
@@ -107,7 +129,19 @@ public class SvnDAVConfig extends DAVConfig
@Override
public String getRepositoryPath()
{
return davConfig.getRepositoryPath();
String path = null;
File directory = getRepositoryDirectory();
if (directory != null)
{
path = directory.getAbsolutePath();
}
else
{
path = davConfig.getRepositoryPath();
}
return path;
}
/**
@@ -227,14 +261,36 @@ public class SvnDAVConfig extends DAVConfig
@Override
public boolean isUsingRepositoryPathDirective()
{
return davConfig.isUsingRepositoryPathDirective();
return true;
}
/**
* Method description
*
*
* @return
*/
private File getRepositoryDirectory()
{
File directory = null;
Repository repository = repositoryProvider.get();
if (repository != null)
{
directory = handler.getDirectory(repository);
}
return directory;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private SvnConfig config;
private DAVConfig davConfig;
/** Field description */
private DAVConfig davConfig;
private SvnRepositoryHandler handler;
/** Field description */
private Provider<Repository> repositoryProvider;
}

View File

@@ -36,12 +36,26 @@ package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.tmatesoft.svn.core.internal.server.dav.DAVConfig;
import org.tmatesoft.svn.core.internal.server.dav.DAVServlet;
import sonia.scm.repository.Repository;
import sonia.scm.repository.SvnRepositoryHandler;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
*
@@ -61,11 +75,34 @@ public class SvnDAVServlet extends DAVServlet
*
*
* @param handler
* @param repositoryProvider
*/
@Inject
public SvnDAVServlet(SvnRepositoryHandler handler)
public SvnDAVServlet(SvnRepositoryHandler handler,
Provider<Repository> repositoryProvider)
{
this.handler = handler;
this.repositoryProvider = repositoryProvider;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param request
* @param response
*
* @throws IOException
* @throws ServletException
*/
@Override
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
super.service(new SvnHttpServletRequestWrapper(request,
repositoryProvider), response);
}
//~--- get methods ----------------------------------------------------------
@@ -79,11 +116,103 @@ public class SvnDAVServlet extends DAVServlet
@Override
protected DAVConfig getDAVConfig()
{
return new SvnDAVConfig(super.getDAVConfig(), handler.getConfig());
return new SvnDAVConfig(super.getDAVConfig(), handler, repositoryProvider);
}
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 11/10/23
* @author Enter your name here...
*/
private static class SvnHttpServletRequestWrapper
extends HttpServletRequestWrapper
{
/**
* Constructs ...
*
*
* @param request
* @param repositoryProvider
*/
public SvnHttpServletRequestWrapper(HttpServletRequest request,
Provider<Repository> repositoryProvider)
{
super(request);
this.repositoryProvider = repositoryProvider;
}
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String getPathInfo()
{
String pathInfo = super.getPathInfo();
AssertUtil.assertIsNotEmpty(pathInfo);
Repository repository = repositoryProvider.get();
if (repository != null)
{
if (pathInfo.startsWith(HttpUtil.SEPARATOR_PATH))
{
pathInfo = pathInfo.substring(1);
}
pathInfo = pathInfo.substring(repository.getName().length());
}
return pathInfo;
}
/**
* Method description
*
*
* @return
*/
@Override
public String getServletPath()
{
String servletPath = super.getServletPath();
Repository repository = repositoryProvider.get();
if (repository != null)
{
if (!servletPath.endsWith(HttpUtil.SEPARATOR_PATH))
{
servletPath = servletPath.concat(HttpUtil.SEPARATOR_PATH);
}
servletPath = servletPath.concat(repository.getName());
}
return servletPath;
}
//~--- fields -------------------------------------------------------------
/** Field description */
private Provider<Repository> repositoryProvider;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private SvnRepositoryHandler handler;
/** Field description */
private Provider<Repository> repositoryProvider;
}

View File

@@ -39,9 +39,8 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.SvnRepositoryHandler;
import sonia.scm.web.filter.RegexPermissionFilter;
import sonia.scm.repository.Repository;
import sonia.scm.web.filter.ProviderPermissionFilter;
import sonia.scm.web.security.WebSecurityContext;
//~--- JDK imports ------------------------------------------------------------
@@ -57,7 +56,7 @@ import javax.servlet.http.HttpServletRequest;
* @author Sebastian Sdorra
*/
@Singleton
public class SvnPermissionFilter extends RegexPermissionFilter
public class SvnPermissionFilter extends ProviderPermissionFilter
{
/** Field description */
@@ -74,30 +73,18 @@ public class SvnPermissionFilter extends RegexPermissionFilter
*
*
* @param securityContextProvider
* @param repositoryManager
* @param repository
*/
@Inject
public SvnPermissionFilter(
Provider<WebSecurityContext> securityContextProvider,
RepositoryManager repositoryManager)
Provider<Repository> repository)
{
super(securityContextProvider, repositoryManager);
super(securityContextProvider, repository);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
protected String getType()
{
return SvnRepositoryHandler.TYPE_NAME;
}
/**
* Method description
*

View File

@@ -61,8 +61,10 @@ import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.ScriptResourceServlet;
import sonia.scm.repository.ChangesetViewerUtil;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryBrowserUtil;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryProvider;
import sonia.scm.repository.xml.XmlRepositoryManager;
import sonia.scm.security.CipherHandler;
import sonia.scm.security.CipherUtil;
@@ -203,6 +205,7 @@ public class ScmServletModule extends ServletModule
ScmConfiguration config = getScmConfiguration(context);
CipherUtil cu = CipherUtil.getInstance();
bind(Repository.class).toProvider(RepositoryProvider.class);
bind(StoreFactory.class).to(JAXBStoreFactory.class);
bind(ScmConfiguration.class).toInstance(config);
bind(PluginLoader.class).toInstance(pluginLoader);

View File

@@ -247,7 +247,8 @@ public class AuthenticationResource
{
return new ScmState(contextProvider, securityContext,
repositoryManger.getConfiguredTypes(),
new ScmClientConfig(configuration.getDateFormat()));
new ScmClientConfig(configuration.getDateFormat(),
configuration.isDisableGroupingGrid()));
}
//~--- fields ---------------------------------------------------------------

View File

@@ -416,11 +416,10 @@ public class RepositoryResource
* @return the {@link Repository} with the specified type and name
*/
@GET
@Path("{type}/{name}")
@Path("{type: [a-z]+}/{name: .*}")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@TypeHint(Repository.class)
public Response getByTypeAndName(@Context Request request,
@PathParam("type") String type,
public Response getByTypeAndName(@PathParam("type") String type,
@PathParam("name") String name)
{
Response response = null;

View File

@@ -0,0 +1,113 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.servlet.RequestScoped;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.http.HttpServletRequest;
/**
*
* @author Sebastian Sdorra
*/
@RequestScoped
public class RepositoryProvider implements Provider<Repository>
{
/** Field description */
public static final String ATTRIBUTE_NAME = "scm.request.repository";
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param requestProvider
* @param manager
*/
@Inject
public RepositoryProvider(Provider<HttpServletRequest> requestProvider,
RepositoryManager manager)
{
this.requestProvider = requestProvider;
this.manager = manager;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public Repository get()
{
Repository repository = null;
HttpServletRequest request = requestProvider.get();
if (request != null)
{
repository = (Repository) request.getAttribute(ATTRIBUTE_NAME);
if (repository == null)
{
repository = manager.getFromRequest(request);
if (repository != null)
{
request.setAttribute(ATTRIBUTE_NAME, repository);
}
}
}
return repository;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private RepositoryManager manager;
/** Field description */
private Provider<HttpServletRequest> requestProvider;
}

View File

@@ -67,6 +67,7 @@ import sonia.scm.store.Store;
import sonia.scm.store.StoreFactory;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.CollectionAppender;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.SecurityUtil;
import sonia.scm.util.Util;
@@ -87,6 +88,8 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
/**
*
* @author Sebastian Sdorra
@@ -625,6 +628,89 @@ public class XmlRepositoryManager extends AbstractRepositoryManager
return viewer;
}
/**
* Method description
*
*
* @param request
*
* @return
*/
@Override
public Repository getFromRequest(HttpServletRequest request)
{
AssertUtil.assertIsNotNull(request);
return getFromUri(HttpUtil.getStrippedURI(request));
}
/**
* Method description
*
*
* @param type
* @param uri
*
* @return
*/
@Override
public Repository getFromTypeAndUri(String type, String uri)
{
AssertUtil.assertIsNotEmpty(type);
AssertUtil.assertIsNotEmpty(uri);
Repository repository = null;
if (handlerMap.containsKey(type))
{
Collection<Repository> repositories = repositoryDB.values();
for (Repository r : repositories)
{
if (type.equals(r.getType()) && isNameMatching(r, uri))
{
if (isReader(r))
{
repository = r.clone();
}
break;
}
}
}
return repository;
}
/**
* Method description
*
*
* @param uri
*
* @return
*/
@Override
public Repository getFromUri(String uri)
{
AssertUtil.assertIsNotEmpty(uri);
if (uri.startsWith(HttpUtil.SEPARATOR_PATH))
{
uri = uri.substring(1);
}
int typeSeperator = uri.indexOf(HttpUtil.SEPARATOR_PATH);
AssertUtil.assertPositive(typeSeperator);
String type = uri.substring(0, typeSeperator);
uri = uri.substring(typeSeperator + 1);
return getFromTypeAndUri(type, uri);
}
/**
* Method description
*
@@ -814,6 +900,30 @@ public class XmlRepositoryManager extends AbstractRepositoryManager
return handler;
}
/**
* Method description
*
*
* @param repository
* @param path
*
* @return
*/
private boolean isNameMatching(Repository repository, String path)
{
boolean result = false;
String name = repository.getName();
if (path.startsWith(name))
{
String sub = path.substring(name.length());
result = Util.isEmpty(sub) || sub.startsWith(HttpUtil.SEPARATOR_PATH);
}
return result;
}
/**
* Method description
*

View File

@@ -69,6 +69,8 @@ public class JAXBStore<T> implements Store<T>
*/
public JAXBStore(Class<T> type, File configFile)
{
this.type = type;
try
{
context = JAXBContext.newInstance(type);
@@ -91,6 +93,11 @@ public class JAXBStore<T> implements Store<T>
@Override
public T get()
{
if (logger.isDebugEnabled())
{
logger.debug("load {} from store {}", type, configFile);
}
T result = null;
if (configFile.exists())
@@ -108,6 +115,17 @@ public class JAXBStore<T> implements Store<T>
return result;
}
/**
* Method description
*
*
* @return
*/
public Class<T> getType()
{
return type;
}
//~--- set methods ----------------------------------------------------------
/**
@@ -145,4 +163,7 @@ public class JAXBStore<T> implements Store<T>
/** Field description */
private JAXBContext context;
/** Field description */
private Class<T> type;
}

View File

@@ -57,6 +57,9 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{
baseUrlText: 'Base Url',
forceBaseUrlText: 'Force Base Url',
// TODO i18n
disableGroupingGridText: 'Disable repository Groups',
// help
servernameHelpText: 'The name of this server. This name will be part of the repository url.',
@@ -79,6 +82,9 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{
proxyPasswordHelpText: 'The password for the proxy server authentication.',
baseUrlHelpText: 'The url of the application (with context path) i.e. http://localhost:8080/scm',
forceBaseUrlHelpText: 'Redirects to the base url if the request comes from a other url',
// TODO i18n
disableGroupingGridHelpText: 'Disable repository Groups. A complete page reload is required after a change of this value.',
initComponent: function(){
@@ -100,6 +106,12 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{
name: 'force-base-url',
inputValue: 'true',
helpText: this.forceBaseUrlHelpText
},{
xtype: 'checkbox',
fieldLabel: this.disableGroupingGridText,
name: 'disableGroupingGrid',
inputValue: 'true',
helpText: this.disableGroupingGridHelpText
},{
xtype: 'textfield',
fieldLabel: this.dateFormatText,

View File

@@ -51,6 +51,13 @@ Ext.apply(Ext.form.VTypes, {
nameText: 'The name is invalid.',
// repository name validator
repositoryName: function(val){
return /^[A-z0-9\.\-_\/]+$/.test(val);
},
repositoryNameText: 'The name of the repository is invalid.',
// username validator
username: function(val){

View File

@@ -42,21 +42,55 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
searchValue: null,
typeFilter: null,
// TODO find better text
mainGroup: 'main',
// for history
parentPanel: null,
initComponent: function(){
var repositoryStore = new Sonia.rest.JsonStore({
var repositoryStore = new Ext.data.GroupingStore({
proxy: new Ext.data.HttpProxy({
url: restUrl + 'repositories.json',
disableCaching: false
}),
idProperty: 'id',
fields: [ 'id', 'name', 'type', 'contact', 'description', 'creationDate', 'url', 'public', 'permissions', 'properties' ],
reader: new Ext.data.JsonReader({
fields: [{
name: 'id'
},{
name: 'group',
convert: this.convertToGroup
},{
name: 'name'
},{
name: 'type'
},{
name: 'contact'
},{
name: 'description'
},{
name: 'creationDate'
},{
name:'url'
},{
name: 'public'
},{
name:'permissions'
},{
name: 'properties'
}]
}),
sortInfo: {
field: 'name'
},
autoDestroy: true,
autoLoad: true,
remoteGroup: false,
groupOnSort: false,
groupField: 'group',
groupDir: 'AES',
listeners: {
load: {
fn: this.storeLoad,
@@ -71,16 +105,58 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
scope: this,
width: 125
},
columns: [
{id: 'name', header: this.colNameText, dataIndex: 'name'},
{id: 'type', header: this.colTypeText, dataIndex: 'type', renderer: this.renderRepositoryType, width: 80},
{id: 'contact', header: this.colContactText, dataIndex: 'contact', renderer: this.renderMailto},
{id: 'description', header: this.colDescriptionText, dataIndex: 'description'},
{id: 'creationDate', header: this.colCreationDateText, dataIndex: 'creationDate', renderer: Ext.util.Format.formatTimestamp},
{id: 'Url', header: this.colUrlText, dataIndex: 'url', renderer: this.renderUrl, width: 250}
]
columns: [{
id: 'name',
header: this.colNameText,
dataIndex: 'name',
renderer: this.renderName,
scope: this
},{
id: 'type',
header: this.colTypeText,
dataIndex: 'type',
renderer: this.renderRepositoryType,
width: 80
},{
id: 'contact',
header: this.colContactText,
dataIndex: 'contact',
renderer: this.renderMailto
},{
id: 'description',
header: this.colDescriptionText,
dataIndex: 'description'
},{
id: 'creationDate',
header: this.colCreationDateText,
dataIndex: 'creationDate',
renderer: Ext.util.Format.formatTimestamp
},{
id: 'Url',
header: this.colUrlText,
dataIndex: 'url',
renderer: this.renderUrl,
width: 250
},{
id: 'group',
dataIndex: 'group',
hidden: true,
hideable: false,
groupRenderer: this.renderGroupName,
scope: this
}]
});
if (debug){
var msg = "grouping is ";
if ( state.clientConfig.disableGroupingGrid ){
msg += "disabled";
} else {
msg += "enabled";
}
console.debug( msg );
}
var config = {
autoExpandColumn: 'description',
store: repositoryStore,
@@ -91,7 +167,15 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
fn: this.onFallBelowMinHeight,
scope: this
}
}
},
view: new Ext.grid.GroupingView({
enableGrouping: ! state.clientConfig.disableGroupingGrid,
enableNoGroups: false,
forceFit: true,
groupMode: 'value',
enableGroupingMenu: false,
groupTextTpl: '{group} ({[values.rs.length]} {[values.rs.length > 1 ? "Repositories" : "Repository"]})'
})
};
Ext.apply(this, Ext.apply(this.initialConfig, config));
@@ -102,6 +186,33 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
}
},
convertToGroup: function(v, data){
var name = data.name;
var i = name.lastIndexOf('/');
if ( i > 0 ){
name = name.substring(0, i);
} else {
name = "zzz__";
}
return name;
},
renderName: function(v, meta, record, rowIndex, colIndex, store){
// TODO check if grouping is enabled
var i = v.lastIndexOf('/');
if ( i > 0 ){
v = v.substring(i+1);
}
return v;
},
renderGroupName: function(v){
if (v == 'zzz__'){
v = this.mainGroup;
}
return v;
},
storeLoad: function(){
if (this.searchValue){
this.filterStore();

View File

@@ -43,7 +43,7 @@ Sonia.repository.SettingsFormPanel = Ext.extend(Sonia.repository.FormPanel, {
readOnly: update,
allowBlank: false,
helpText: this.nameHelpText,
vtype: 'name'
vtype: 'repositoryName'
},{
fieldLabel: this.typeText,
name: 'type',

View File

@@ -0,0 +1,193 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.it;
//~--- non-JDK imports --------------------------------------------------------
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.util.IOUtil;
import static org.junit.Assert.*;
import static sonia.scm.it.IntegrationTestUtil.*;
import static sonia.scm.it.RepositoryITUtil.*;
//~--- JDK imports ------------------------------------------------------------
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
/**
*
* @author Sebastian Sdorra
*/
@RunWith(Parameterized.class)
public class CreateRepositoriesITCase extends AbstractAdminITCaseBase
{
/**
* Constructs ...
*
*
* @param repositoryType
*/
public CreateRepositoriesITCase(String repositoryType)
{
System.out.append("==> CreateRepositoriesITCase - ").println(
repositoryType);
this.repositoryType = repositoryType;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Parameters
public static Collection<String[]> createParameters()
{
Collection<String[]> params = new ArrayList<String[]>();
params.add(new String[] { "git" });
params.add(new String[] { "git" });
if (IOUtil.search("hg") != null)
{
params.add(new String[] { "git" });
}
return params;
}
/**
* Method description
*
*
* @throws IOException
*/
@After
public void cleanup() throws IOException
{
deleteRepository(client, repository.getId());
}
/**
* Method description
*
*/
@Test
public void testCreate()
{
repository = RepositoryTestData.createHeartOfGold(repositoryType);
repository = createRepository(client, repository);
}
/**
* Method description
*
*/
@Test
public void testCreateAllreadyExists()
{
repository = RepositoryTestData.create42Puzzle(repositoryType);
repository = createRepository(client, repository);
WebResource wr = createResource(client, "repositories");
ClientResponse response =
wr.post(ClientResponse.class,
RepositoryTestData.create42Puzzle(repositoryType));
assertNotNull(response);
assertEquals(500, response.getStatus());
response.close();
}
/**
* Method description
*
*/
@Test
public void testCreateAllreadyExistsWithStructure()
{
repository = RepositoryTestData.create42Puzzle(repositoryType);
repository = createRepository(client, repository);
Repository r = RepositoryTestData.create42Puzzle(repositoryType);
r.setName(r.getName() + "/" + r.getName());
WebResource wr = createResource(client, "repositories");
ClientResponse response = wr.post(ClientResponse.class, r);
assertNotNull(response);
System.out.println( response.getStatus() );
assertEquals(500, response.getStatus());
response.close();
}
/**
* Method description
*
*/
@Test
public void testCreateWithStructure()
{
repository = RepositoryTestData.createHeartOfGold(repositoryType);
repository.setName("test/".concat(repository.getName()));
repository = createRepository(client, repository);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private Repository repository;
/** Field description */
private String repositoryType;
}

View File

@@ -0,0 +1,196 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.it;
//~--- non-JDK imports --------------------------------------------------------
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.util.IOUtil;
import static org.junit.Assert.*;
import static sonia.scm.it.IntegrationTestUtil.*;
import static sonia.scm.it.RepositoryITUtil.*;
//~--- JDK imports ------------------------------------------------------------
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
/**
*
* @author Sebastian Sdorra
*/
@RunWith(Parameterized.class)
public class GetRepositoriesITCase extends AbstractAdminITCaseBase
{
/**
* Constructs ...
*
*
* @param repositoryType
*/
public GetRepositoriesITCase(String repositoryType)
{
this.repositoryType = repositoryType;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Parameters
public static Collection<String[]> createParameters()
{
Collection<String[]> params = new ArrayList<String[]>();
params.add(new String[] { "git" });
params.add(new String[] { "svn" });
if (IOUtil.search("hg") != null)
{
params.add(new String[] { "hg" });
}
return params;
}
/**
* Method description
*
*
* @throws IOException
*/
@After
public void cleanup() throws IOException
{
deleteRepository(client, repository.getId());
}
/**
* Method description
*
*/
@Test
public void testGetById()
{
repository = RepositoryTestData.createHeartOfGold(repositoryType);
repository = createRepository(client, repository);
String id = repository.getId();
assertNotNull(id);
Repository r = getRepositoryById(client, id);
assertEquals(id, r.getId());
}
/**
* Method description
*
*/
@Test
public void testGetByTypeAndName()
{
repository = RepositoryTestData.create42Puzzle(repositoryType);
testGetByTypeAndName(repository);
}
/**
* Method description
*
*/
@Test
public void testGetByTypeAndNameWithDirectoryStructure()
{
repository =
RepositoryTestData.createRestaurantAtTheEndOfTheUniverse(repositoryType);
repository.setName("test/".concat(repository.getName()));
testGetByTypeAndName(repository);
}
/**
* Method description
*
*
* @param repository
*/
private void testGetByTypeAndName(Repository repo)
{
repository = createRepository(client, repo);
String name = repository.getName();
WebResource wr = createResource(
client,
"repositories/".concat(repositoryType).concat(
"/").concat(name));
ClientResponse response = wr.get(ClientResponse.class);
assertNotNull(response);
Repository r = response.getEntity(Repository.class);
response.close();
assertNotNull(r);
assertEquals(repository.getId(), r.getId());
assertEquals(repository.getName(), r.getName());
assertEquals(repository.getType(), r.getType());
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private Repository repository;
/** Field description */
private String repositoryType;
}

View File

@@ -50,6 +50,7 @@ import sonia.scm.repository.client.RepositoryClientFactory;
import sonia.scm.user.User;
import sonia.scm.user.UserTestData;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
import static org.junit.Assert.*;
@@ -220,36 +221,7 @@ public class RepositoryITCaseBase
for (Type t : state.getRepositoryTypes())
{
if (t.getName().equals("git"))
{
Repository gitRepository = createTestRepository("git", owner, write,
read);
params.add(new Object[]
{
gitRepository, owner, write, read, noperm, "secret"
});
}
else if (t.getName().equals("svn"))
{
Repository svnRepository = createTestRepository("svn", owner, write,
read);
params.add(new Object[]
{
svnRepository, owner, write, read, noperm, "secret"
});
}
else if (t.getName().equals("hg"))
{
Repository hgRepository = createTestRepository("hg", owner, write,
read);
params.add(new Object[]
{
hgRepository, owner, write, read, noperm, "secret"
});
}
appendTestParemeter(params, t.getName(), owner, write, read, noperm);
}
return params;
@@ -259,6 +231,40 @@ public class RepositoryITCaseBase
* Method description
*
*
* @param params
* @param type
* @param owner
* @param write
* @param read
* @param noperm
*
* @throws IOException
* @throws RepositoryClientException
*/
private static void appendTestParemeter(Collection<Object[]> params,
String type, User owner, User write, User read, User noperm)
throws RepositoryClientException, IOException
{
Repository repository = createTestRepository(null, type, owner, write,
read);
params.add(new Object[]
{
repository, owner, write, read, noperm, "secret"
});
repository = createTestRepository("test", type, owner, write, read);
params.add(new Object[]
{
repository, owner, write, read, noperm, "secret"
});
}
/**
* Method description
*
*
*
* @param prefix
* @param type
* @param owner
* @param write
@@ -269,13 +275,18 @@ public class RepositoryITCaseBase
* @throws IOException
* @throws RepositoryClientException
*/
private static Repository createTestRepository(String type, User owner,
User write, User read)
private static Repository createTestRepository(String prefix, String type,
User owner, User write, User read)
throws RepositoryClientException, IOException
{
Client client = createAdminClient();
Repository repository = RepositoryTestData.createHeartOfGold(type);
if (Util.isNotEmpty(prefix))
{
repository.setName(prefix.concat("/").concat(repository.getName()));
}
//J-
repository.setPermissions(Arrays.asList(
new Permission(owner.getName(), PermissionType.OWNER),

View File

@@ -37,16 +37,22 @@ package sonia.scm.repository;
import com.google.inject.Provider;
import sonia.scm.Manager;
import org.junit.Test;
import sonia.scm.Type;
import sonia.scm.repository.xml.XmlRepositoryManager;
import sonia.scm.store.JAXBStoreFactory;
import sonia.scm.store.StoreFactory;
import sonia.scm.util.MockUtil;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
@@ -57,6 +63,36 @@ import java.util.Set;
public class XmlRepositoryManagerTest extends RepositoryManagerTestBase
{
/**
* Method description
*
*
* @throws IOException
* @throws RepositoryException
*/
@Test
public void getRepositoryFromRequestUriTest()
throws RepositoryException, IOException
{
RepositoryManager m = createManager();
m.init(contextProvider);
createRepository(m, new Repository("1", "hg", "scm"));
createRepository(m, new Repository("2", "hg", "scm-test"));
createRepository(m, new Repository("3", "git", "project1/test-1"));
createRepository(m, new Repository("4", "git", "project1/test-2"));
assertEquals("scm", m.getFromUri("hg/scm").getName());
assertEquals("scm-test", m.getFromUri("hg/scm-test").getName());
assertEquals("scm-test", m.getFromUri("/hg/scm-test").getName());
assertEquals("project1/test-1",
m.getFromUri("/git/project1/test-1").getName());
assertEquals("project1/test-1",
m.getFromUri("/git/project1/test-1/ka/some/path").getName());
assertNull(m.getFromUri("/git/project1/test-3/ka/some/path"));
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
@@ -64,13 +100,29 @@ public class XmlRepositoryManagerTest extends RepositoryManagerTestBase
* @return
*/
@Override
protected Manager<Repository, RepositoryException> createManager()
protected XmlRepositoryManager createManager()
{
Set<RepositoryHandler> handlerSet = new HashSet<RepositoryHandler>();
StoreFactory factory = new JAXBStoreFactory();
factory.init(contextProvider);
handlerSet.add(new DummyRepositoryHandler(factory));
handlerSet.add(new DummyRepositoryHandler(factory)
{
@Override
public Type getType()
{
return new Type("hg", "Mercurial");
}
});
handlerSet.add(new DummyRepositoryHandler(factory)
{
@Override
public Type getType()
{
return new Type("git", "Git");
}
});
Provider<Set<RepositoryListener>> listenerProvider = mock(Provider.class);
@@ -85,4 +137,20 @@ public class XmlRepositoryManagerTest extends RepositoryManagerTestBase
factory, handlerSet, listenerProvider,
hookProvider);
}
/**
* Method description
*
*
* @param m
* @param repository
*
* @throws IOException
* @throws RepositoryException
*/
private void createRepository(RepositoryManager m, Repository repository)
throws RepositoryException, IOException
{
m.create(repository);
}
}