merge with branch issue-873

This commit is contained in:
Sebastian Sdorra
2016-11-24 13:38:30 +01:00
27 changed files with 895 additions and 171 deletions

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2014, 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;
import sonia.scm.event.Event;
/**
* Event which causes clearing of repository cache.
*
* @author Sebastian Sdorra
* @since 1.50
*/
@Event
public class ClearRepositoryCacheEvent {
private final Repository repository;
/**
* Constructs a new instance.
*
* @param repository repository
*/
public ClearRepositoryCacheEvent(Repository repository) {
this.repository = repository;
}
/**
* Returns repository.
*
* @return repository
*/
public Repository getRepository() {
return repository;
}
}

View File

@@ -37,6 +37,8 @@ package sonia.scm.repository.api;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -69,6 +71,8 @@ import sonia.scm.security.ScmSecurityException;
//~--- JDK imports ------------------------------------------------------------
import java.util.Set;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.ClearRepositoryCacheEvent;
/**
* The {@link RepositoryServiceFactory} is the entrypoint of the repository api.
@@ -172,6 +176,9 @@ public final class RepositoryServiceFactory
repositoryManager.addHook(cch);
repositoryManager.addListener(cch);
// register cache clear hook for incoming events
ScmEventBus.getInstance().register(cch);
}
//~--- methods --------------------------------------------------------------
@@ -310,46 +317,60 @@ public final class RepositoryServiceFactory
//~--- inner classes --------------------------------------------------------
/**
* TODO find a more elegant way
*
*
* @version Enter version here..., 12/06/16
* @author Enter your name here...
* Hook and listener to clear all relevant repository caches.
*/
private static class CacheClearHook extends PostReceiveRepositoryHook
implements RepositoryListener
{
private final Set<Cache<?, ?>> caches = Sets.newHashSet();
/**
* Constructs ...
* Constructs a new instance and collect all repository relevant
* caches from the {@link CacheManager}.
*
*
* @param cacheManager
* @param cacheManager cache manager
*/
public CacheClearHook(CacheManager cacheManager)
{
this.blameCache =
cacheManager.getCache(BlameCommandBuilder.CacheKey.class,
BlameResult.class, BlameCommandBuilder.CACHE_NAME);
this.browseCache =
cacheManager.getCache(BrowseCommandBuilder.CacheKey.class,
BrowserResult.class, BrowseCommandBuilder.CACHE_NAME);
this.logCache = cacheManager.getCache(LogCommandBuilder.CacheKey.class,
ChangesetPagingResult.class, LogCommandBuilder.CACHE_NAME);
this.tagsCache = cacheManager.getCache(TagsCommandBuilder.CacheKey.class,
Tags.class, TagsCommandBuilder.CACHE_NAME);
this.branchesCache =
cacheManager.getCache(BranchesCommandBuilder.CacheKey.class,
Branches.class, BranchesCommandBuilder.CACHE_NAME);
this.caches.add(cacheManager.getCache(
BlameCommandBuilder.CacheKey.class,
BlameResult.class, BlameCommandBuilder.CACHE_NAME)
);
this.caches.add(cacheManager.getCache(
BrowseCommandBuilder.CacheKey.class,
BrowserResult.class, BrowseCommandBuilder.CACHE_NAME)
);
this.caches.add(cacheManager.getCache(
LogCommandBuilder.CacheKey.class,
ChangesetPagingResult.class, LogCommandBuilder.CACHE_NAME)
);
this.caches.add(cacheManager.getCache(
TagsCommandBuilder.CacheKey.class,
Tags.class, TagsCommandBuilder.CACHE_NAME)
);
this.caches.add(cacheManager.getCache(
BranchesCommandBuilder.CacheKey.class,
Branches.class, BranchesCommandBuilder.CACHE_NAME)
);
}
//~--- methods ------------------------------------------------------------
/**
* Method description
* Clear caches on explicit repository cache clear event.
*
* @param event clear event
*/
@Subscribe
public void onEvent(ClearRepositoryCacheEvent event) {
clearCaches(event.getRepository().getId());
}
/**
* Clear caches on repository push.
*
*
* @param event
* @param event hook event
*/
@Override
public void onEvent(RepositoryHookEvent event)
@@ -365,11 +386,10 @@ public final class RepositoryServiceFactory
}
/**
* Method description
* Clear caches on repository delete event.
*
*
* @param repository
* @param event
* @param repository changed repository
* @param event repository event
*/
@Override
public void onEvent(Repository repository, HandlerEvent event)
@@ -379,13 +399,7 @@ public final class RepositoryServiceFactory
clearCaches(repository.getId());
}
}
/**
* Method description
*
*
* @param repositoryId
*/
@SuppressWarnings("unchecked")
private void clearCaches(final String repositoryId)
{
@@ -394,32 +408,11 @@ public final class RepositoryServiceFactory
logger.debug("clear caches for repository id {}", repositoryId);
}
RepositoryCacheKeyFilter filter =
new RepositoryCacheKeyFilter(repositoryId);
blameCache.removeAll(filter);
browseCache.removeAll(filter);
logCache.removeAll(filter);
tagsCache.removeAll(filter);
branchesCache.removeAll(filter);
RepositoryCacheKeyFilter filter = new RepositoryCacheKeyFilter(repositoryId);
for (Cache<?,?> cache : caches) {
cache.removeAll(filter);
}
}
//~--- fields -------------------------------------------------------------
/** Field description */
private Cache<BlameCommandBuilder.CacheKey, BlameResult> blameCache;
/** Field description */
private Cache<BranchesCommandBuilder.CacheKey, Branches> branchesCache;
/** Field description */
private Cache<BrowseCommandBuilder.CacheKey, BrowserResult> browseCache;
/** Field description */
private Cache<LogCommandBuilder.CacheKey, ChangesetPagingResult> logCache;
/** Field description */
private Cache<TagsCommandBuilder.CacheKey, Tags> tagsCache;
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2014, 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;
/**
* Constants for Git.
*
* @author Sebastian Sdorra
* @since 1.50
*/
public final class GitConstants {
/**
* Default branch repository property.
*/
public static final String PROPERTY_DEFAULT_BRANCH = "git.default-branch";
private GitConstants() {
}
}

View File

@@ -0,0 +1,97 @@
/**
* Copyright (c) 2014, 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;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.eventbus.Subscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton;
import sonia.scm.HandlerEvent;
import sonia.scm.event.ScmEventBus;
import sonia.scm.plugin.ext.Extension;
/**
* Repository listener which handles git related repository events.
*
* @author Sebastian Sdorra
* @since 1.50
*/
@Extension
@EagerSingleton
public class GitRepositoryModifyListener {
/**
* the logger for GitRepositoryModifyListener
*/
private static final Logger logger = LoggerFactory.getLogger(GitRepositoryModifyListener.class);
/**
* Receives {@link RepositoryModificationEvent} and fires a {@link ClearRepositoryCacheEvent} if
* the default branch of a git repository was modified.
*
* @param event repository modification event
*/
@Subscribe
public void handleEvent(RepositoryModificationEvent event){
Repository repository = event.getItem();
if ( isModifyEvent(event) &&
isGitRepository(event.getItem()) &&
hasDefaultBranchChanged(event.getItemBeforeModification(), repository))
{
logger.info("git default branch of repository {} has changed, sending clear cache event", repository.getId());
sendClearRepositoryCacheEvent(repository);
}
}
@VisibleForTesting
protected void sendClearRepositoryCacheEvent(Repository repository) {
ScmEventBus.getInstance().post(new ClearRepositoryCacheEvent(repository));
}
private boolean isModifyEvent(RepositoryEvent event) {
return event.getEventType() == HandlerEvent.MODIFY;
}
private boolean isGitRepository(Repository repository) {
return GitRepositoryHandler.TYPE_NAME.equals(repository.getType());
}
private boolean hasDefaultBranchChanged(Repository old, Repository current) {
return !Objects.equal(
old.getProperty(GitConstants.PROPERTY_DEFAULT_BRANCH),
current.getProperty(GitConstants.PROPERTY_DEFAULT_BRANCH)
);
}
}

View File

@@ -34,11 +34,18 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import org.eclipse.jgit.lib.Repository;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import org.eclipse.jgit.lib.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitConstants;
import sonia.scm.repository.GitUtil;
/**
*
@@ -46,6 +53,11 @@ import java.io.IOException;
*/
public class AbstractGitCommand
{
/**
* the logger for AbstractGitCommand
*/
private static final Logger logger = LoggerFactory.getLogger(AbstractGitCommand.class);
/**
* Constructs ...
@@ -54,7 +66,6 @@ public class AbstractGitCommand
*
* @param context
* @param repository
* @param repositoryDirectory
*/
protected AbstractGitCommand(GitContext context,
sonia.scm.repository.Repository repository)
@@ -77,6 +88,38 @@ public class AbstractGitCommand
{
return context.open();
}
protected ObjectId getCommitOrDefault(Repository gitRepository, String requestedCommit) throws IOException {
ObjectId commit;
if ( Strings.isNullOrEmpty(requestedCommit) ) {
commit = getDefaultBranch(gitRepository);
} else {
commit = gitRepository.resolve(requestedCommit);
}
return commit;
}
protected ObjectId getBranchOrDefault(Repository gitRepository, String requestedBranch) throws IOException {
ObjectId head;
if ( Strings.isNullOrEmpty(requestedBranch) ) {
head = getDefaultBranch(gitRepository);
} else {
head = GitUtil.getBranchId(gitRepository, requestedBranch);
}
return head;
}
protected ObjectId getDefaultBranch(Repository gitRepository) throws IOException {
ObjectId head;
String defaultBranchName = repository.getProperty(GitConstants.PROPERTY_DEFAULT_BRANCH);
if (!Strings.isNullOrEmpty(defaultBranchName)) {
head = GitUtil.getBranchId(gitRepository, defaultBranchName);
} else {
logger.trace("no default branch configured, use repository head as default");
head = GitUtil.getRepositoryHead(gitRepository);
}
return head;
}
//~--- fields ---------------------------------------------------------------

View File

@@ -138,7 +138,7 @@ public abstract class AbstractGitIncomingOutgoingCommand
GitUtil.fetch(git, handler.getDirectory(remoteRepository), remoteRepository);
ObjectId localId = GitUtil.getRepositoryHead(git.getRepository());
ObjectId localId = getDefaultBranch(git.getRepository());
ObjectId remoteId = null;
Ref remoteBranch = getRemoteBranch(git.getRepository(), localId,

View File

@@ -124,7 +124,7 @@ public class GitBlameCommand extends AbstractGitCommand implements BlameCommand
blame.setFilePath(request.getPath());
ObjectId revId = GitUtil.getRevisionId(gr, request.getRevision());
ObjectId revId = getCommitOrDefault(gr, request.getRevision());
blame.setStartCommit(revId);

View File

@@ -45,7 +45,6 @@ import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.submodule.SubmoduleWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
@@ -72,7 +71,6 @@ import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
*
@@ -96,11 +94,8 @@ public class GitBrowseCommand extends AbstractGitCommand
/**
* Constructs ...
*
*
*
* @param context
* @param repository
* @param repositoryDirectory
*/
public GitBrowseCommand(GitContext context, Repository repository)
{
@@ -124,18 +119,15 @@ public class GitBrowseCommand extends AbstractGitCommand
public BrowserResult getBrowserResult(BrowseCommandRequest request)
throws IOException, RepositoryException
{
if (logger.isDebugEnabled())
{
logger.debug("try to create browse result for {}", request);
}
logger.debug("try to create browse result for {}", request);
BrowserResult result = null;
BrowserResult result;
org.eclipse.jgit.lib.Repository repo = open();
ObjectId revId = null;
ObjectId revId;
if (Util.isEmpty(request.getRevision()))
{
revId = GitUtil.getRepositoryHead(repo);
revId = getDefaultBranch(repo);
}
else
{

View File

@@ -34,6 +34,7 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Strings;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
@@ -78,7 +79,6 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand
*
* @param context
* @param repository
* @param repositoryDirectory
*/
public GitCatCommand(GitContext context,
sonia.scm.repository.Repository repository)
@@ -102,17 +102,11 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand
public void getCatResult(CatCommandRequest request, OutputStream output)
throws IOException, RepositoryException
{
if (logger.isDebugEnabled())
{
logger.debug("try to read content for {}", request);
}
org.eclipse.jgit.lib.Repository repo = null;
repo = open();
ObjectId revId = GitUtil.getRevisionId(repo, request.getRevision());
logger.debug("try to read content for {}", request);
org.eclipse.jgit.lib.Repository repo = open();
ObjectId revId = getCommitOrDefault(repo, request.getRevision());
getContent(repo, revId, request.getPath(), output);
}

View File

@@ -79,7 +79,6 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand
*
* @param context
* @param repository
* @param repositoryDirectory
*/
public GitDiffCommand(GitContext context, Repository repository)
{

View File

@@ -220,17 +220,8 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
AndTreeFilter.create(
PathFilter.create(request.getPath()), TreeFilter.ANY_DIFF));
}
ObjectId head = null;
if (!Strings.isNullOrEmpty(request.getBranch()))
{
head = GitUtil.getBranchId(gr, request.getBranch());
}
else
{
head = GitUtil.getRepositoryHead(gr);
}
ObjectId head = getBranchOrDefault(gr, request.getBranch());
if (head != null)
{

View File

@@ -90,6 +90,93 @@ Sonia.git.ConfigPanel = Ext.extend(Sonia.config.SimpleConfigForm, {
Ext.reg("gitConfigPanel", Sonia.git.ConfigPanel);
// add default branch chooser to settings panel
Sonia.git.GitSettingsFormPanel = Ext.extend(Sonia.repository.SettingsFormPanel, {
defaultBranchText: 'Default Branch',
defaultBranchHelpText: 'The default branch which is show first on source or commit view.',
modifyDefaultConfig: function(config){
if (this.item) {
var position = -1;
for ( var i=0; i<config.items.length; i++ ) {
var field = config.items[i];
if (field.name === 'public') {
position = i;
break;
}
}
var defaultBranchComboxBox = {
fieldLabel: this.defaultBranchText,
name: 'defaultBranch',
repositoryId: this.item.id,
value: this.getDefaultBranch(this.item),
useNameAsValue: true,
xtype: 'repositoryBranchComboBox',
helpText: this.defaultBranchHelpText
};
if (position >= 0) {
config.items.splice(position, 0, defaultBranchComboxBox);
} else {
config.items.push(defaultBranchComboxBox);
}
}
},
getDefaultBranch: function(item){
if (item.properties) {
for ( var i=0; i<item.properties.length; i++ ) {
var prop = item.properties[i];
if (prop.key === 'git.default-branch') {
return prop.value;
}
}
}
return undefined;
},
setDefaultBranch: function(item, defaultBranch){
if (!item.properties) {
item.properties = [{
key: 'git.default-branch',
value: defaultBranch
}];
} else {
var found = false;
for ( var i=0; i<item.properties.length; i++ ) {
var prop = item.properties[i];
if (prop.key === 'git.default-branch') {
prop.value = defaultBranch;
found = true;
break;
}
}
if (!found) {
item.properties.push({
key: 'git.default-branch',
value: defaultBranch
});
}
}
},
prepareUpdate: function(item) {
if (item.defaultBranch) {
var defaultBranch = item.defaultBranch;
delete item.defaultBranch;
this.setDefaultBranch(item, defaultBranch);
}
}
});
Ext.reg("gitSettingsForm", Sonia.git.GitSettingsFormPanel);
// i18n
if ( i18n && i18n.country === 'de' ){
@@ -107,9 +194,21 @@ if ( i18n && i18n.country === 'de' ){
Die Seite muss neu geladen werden wenn dieser Wert geändert wird.'
});
Ext.override(Sonia.git.GitSettingsFormPanel, {
// labels
defaultBranchText: 'Standard Branch',
// helpTexts
defaultBranchHelpText: 'Der Standard Branch wird für die Source und Commit Ansicht verwendet, \n\
wenn kein anderer Branch eingestellt wurde.'
});
}
// register information panel
initCallbacks.push(function(main){
@@ -117,6 +216,9 @@ initCallbacks.push(function(main){
checkoutTemplate: 'git clone <a href="{0}" target="_blank">{0}</a>',
xtype: 'repositoryExtendedInfoPanel'
});
main.registerSettingsForm('git', {
xtype: 'gitSettingsForm'
});
});
// register panel

View File

@@ -0,0 +1,163 @@
/**
* Copyright (c) 2014, 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;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Before;
import sonia.scm.HandlerEvent;
/**
* Unit tests for {@link GitRepositoryModifyListener}.
*
* @author Sebastian Sdorra
*/
public class GitRepositoryModifyListenerTest {
private GitRepositoryModifyTestListener repositoryModifyListener;
/**
* Set up test object.
*/
@Before
public void setUpObjectUnderTest(){
repositoryModifyListener = new GitRepositoryModifyTestListener();
}
/**
* Tests happy path.
*/
@Test
public void testHandleEvent() {
Repository old = RepositoryTestData.createHeartOfGold("git");
old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master");
Repository current = RepositoryTestData.createHeartOfGold("git");
current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop");
RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY);
repositoryModifyListener.handleEvent(event);
assertNotNull(repositoryModifyListener.repository);
assertSame(current, repositoryModifyListener.repository);
}
/**
* Tests with new default branch.
*/
@Test
public void testWithNewDefaultBranch() {
Repository old = RepositoryTestData.createHeartOfGold("git");
Repository current = RepositoryTestData.createHeartOfGold("git");
current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop");
RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY);
repositoryModifyListener.handleEvent(event);
assertNotNull(repositoryModifyListener.repository);
assertSame(current, repositoryModifyListener.repository);
}
/**
* Tests with non git repositories.
*/
@Test
public void testNonGitRepository(){
Repository old = RepositoryTestData.createHeartOfGold("hg");
old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master");
Repository current = RepositoryTestData.createHeartOfGold("hg");
current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop");
RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY);
repositoryModifyListener.handleEvent(event);
assertNull(repositoryModifyListener.repository);
}
/**
* Tests without default branch.
*/
@Test
public void testWithoutDefaultBranch(){
Repository old = RepositoryTestData.createHeartOfGold("git");
Repository current = RepositoryTestData.createHeartOfGold("git");
RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY);
repositoryModifyListener.handleEvent(event);
assertNull(repositoryModifyListener.repository);
}
/**
* Tests with non modify event.
*/
@Test
public void testNonModifyEvent(){
Repository old = RepositoryTestData.createHeartOfGold("git");
old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master");
Repository current = RepositoryTestData.createHeartOfGold("git");
current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "develop");
RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.CREATE);
repositoryModifyListener.handleEvent(event);
assertNull(repositoryModifyListener.repository);
}
/**
* Tests with non git repositories.
*/
@Test
public void testNoModification(){
Repository old = RepositoryTestData.createHeartOfGold("git");
old.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master");
Repository current = RepositoryTestData.createHeartOfGold("git");
current.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "master");
RepositoryModificationEvent event = new RepositoryModificationEvent(current, old, HandlerEvent.MODIFY);
repositoryModifyListener.handleEvent(event);
assertNull(repositoryModifyListener.repository);
}
private static class GitRepositoryModifyTestListener extends GitRepositoryModifyListener {
private Repository repository;
@Override
protected void sendClearRepositoryCacheEvent(Repository repository) {
this.repository = repository;
}
}
}

View File

@@ -45,14 +45,42 @@ import static org.junit.Assert.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import sonia.scm.repository.GitConstants;
/**
*
* Unit tests for {@link GitBlameCommand}.
*
* @author Sebastian Sdorra
*/
public class GitBlameCommandTest extends AbstractGitCommandTestBase
{
/**
* Tests blame command with default branch.
*
* @throws IOException
* @throws RepositoryException
*/
@Test
public void testDefaultBranch() throws IOException, RepositoryException {
// without default branch, the repository head should be used
BlameCommandRequest request = new BlameCommandRequest();
request.setPath("a.txt");
BlameResult result = createCommand().getBlameResult(request);
assertNotNull(result);
assertEquals(2, result.getTotal());
assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", result.getLine(0).getRevision());
assertEquals("fcd0ef1831e4002ac43ea539f4094334c79ea9ec", result.getLine(1).getRevision());
// set default branch and test again
repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch");
result = createCommand().getBlameResult(request);
assertNotNull(result);
assertEquals(1, result.getTotal());
assertEquals("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4", result.getLine(0).getRevision());
}
/**
* Method description
*

View File

@@ -48,13 +48,51 @@ import static org.junit.Assert.*;
import java.io.IOException;
import java.util.List;
import sonia.scm.repository.GitConstants;
/**
*
* Unit tests for {@link GitBrowseCommand}.
*
* @author Sebastian Sdorra
*/
public class GitBrowseCommandTest extends AbstractGitCommandTestBase
{
/**
* Test browse command with default branch.
*
* @throws IOException
* @throws RepositoryException
*/
@Test
public void testDefaultBranch() throws IOException, RepositoryException {
// without default branch, the repository head should be used
BrowserResult result = createCommand().getBrowserResult(new BrowseCommandRequest());
assertNotNull(result);
List<FileObject> foList = result.getFiles();
assertNotNull(foList);
assertFalse(foList.isEmpty());
assertEquals(4, foList.size());
assertEquals("a.txt", foList.get(0).getName());
assertEquals("b.txt", foList.get(1).getName());
assertEquals("c", foList.get(2).getName());
assertEquals("f.txt", foList.get(3).getName());
// set default branch and fetch again
repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch");
result = createCommand().getBrowserResult(new BrowseCommandRequest());
assertNotNull(result);
foList = result.getFiles();
assertNotNull(foList);
assertFalse(foList.isEmpty());
assertEquals(2, foList.size());
assertEquals("a.txt", foList.get(0).getName());
assertEquals("c", foList.get(1).getName());
}
/**
* Method description

View File

@@ -44,14 +44,36 @@ import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import sonia.scm.repository.GitConstants;
/**
* Unit tests for {@link GitCatCommand}.
*
* TODO add not found test
*
* @author Sebastian Sdorra
*/
public class GitCatCommandTest extends AbstractGitCommandTestBase
{
/**
* Tests cat command with default branch.
*
* @throws IOException
* @throws RepositoryException
*/
@Test
public void testDefaultBranch() throws IOException, RepositoryException {
// without default branch, the repository head should be used
CatCommandRequest request = new CatCommandRequest();
request.setPath("a.txt");
assertEquals("a\nline for blame", execute(request));
// set default branch for repository and check again
repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch");
assertEquals("a and b", execute(request));
}
/**
* Method description

View File

@@ -49,14 +49,48 @@ import static org.junit.Assert.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import org.eclipse.jgit.api.errors.GitAPIException;
import sonia.scm.repository.GitConstants;
/**
*
* Unit tests for {@link GitLogCommand}.
*
* @author Sebastian Sdorra
*/
public class GitLogCommandTest extends AbstractGitCommandTestBase
{
/**
* Tests log command with the usage of a default branch.
*
* @throws IOException
* @throws GitAPIException
* @throws RepositoryException
*/
@Test
public void testGetDefaultBranch() throws IOException, GitAPIException, RepositoryException {
// without default branch, the repository head should be used
ChangesetPagingResult result = createCommand().getChangesets(new LogCommandRequest());
assertNotNull(result);
assertEquals(4, result.getTotal());
assertEquals("fcd0ef1831e4002ac43ea539f4094334c79ea9ec", result.getChangesets().get(0).getId());
assertEquals("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1", result.getChangesets().get(1).getId());
assertEquals("592d797cd36432e591416e8b2b98154f4f163411", result.getChangesets().get(2).getId());
assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", result.getChangesets().get(3).getId());
// set default branch and fetch again
repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch");
result = createCommand().getChangesets(new LogCommandRequest());
assertNotNull(result);
assertEquals(3, result.getTotal());
assertEquals("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4", result.getChangesets().get(0).getId());
assertEquals("592d797cd36432e591416e8b2b98154f4f163411", result.getChangesets().get(1).getId());
assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", result.getChangesets().get(2).getId());
}
/**
* Method description
*

View File

@@ -51,7 +51,8 @@ import static org.junit.Assert.assertNotNull;
import java.io.IOException;
/**
*
* Unit tests for {@link OutgoingCommand}.
*
* @author Sebastian Sdorra
*/
public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase

View File

@@ -115,6 +115,8 @@
<!-- sonia.repository -->
<script type="text/javascript" src="resources/js/repository/sonia.repository.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.branchcombobox.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.tagcombobox.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.grid.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.infopanel.js"></script>
<script type="text/javascript" src="resources/js/repository/sonia.repository.extendedinfopanel.js"></script>

View File

@@ -0,0 +1,66 @@
/* *
* Copyright (c) 2014, 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
*
*/
Sonia.repository.BranchComboBox = Ext.extend(Ext.form.ComboBox, {
repositoryId: null,
useNameAsValue: false,
initComponent: function(){
var branchStore = new Sonia.rest.JsonStore({
proxy: new Ext.data.HttpProxy({
url: restUrl + 'repositories/' + this.repositoryId + '/branches.json',
method: 'GET',
disableCaching: false
}),
root: 'branch',
idProperty: 'name',
fields: ['name', 'revision']
});
var config = {
displayField: 'name',
valueField: this.useNameAsValue ? 'name' : 'revision',
typeAhead: false,
editable: false,
triggerAction: 'all',
store: branchStore
};
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.repository.BranchComboBox.superclass.initComponent.apply(this, arguments);
}
});
// register xtype
Ext.reg('repositoryBranchComboBox', Sonia.repository.BranchComboBox);

View File

@@ -120,33 +120,14 @@ Sonia.repository.ChangesetViewerPanel = Ext.extend(Ext.Panel, {
},
createTopToolbar: function(){
var branchStore = new Sonia.rest.JsonStore({
proxy: new Ext.data.HttpProxy({
url: restUrl + 'repositories/' + this.repository.id + '/branches.json',
method: 'GET',
disableCaching: false
}),
root: 'branch',
idProperty: 'name',
fields: [ 'name' ],
sortInfo: {
field: 'name'
}
});
return {
xtype: 'toolbar',
items: [
this.repository.name,
'->',
'Branches:', ' ',{
xtype: 'combo',
valueField: 'name',
displayField: 'name',
typeAhead: false,
editable: false,
triggerAction: 'all',
store: branchStore,
xtype: 'repositoryBranchComboBox',
repositoryId: this.repository.id,
listeners: {
select: {
fn: this.selectBranch,

View File

@@ -61,8 +61,15 @@ Sonia.repository.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
Sonia.repository.FormPanel.superclass.initComponent.apply(this, arguments);
},
prepareUpdate: function(item) {
},
update: function(item){
item = Ext.apply( this.item, item );
// allow plugins to modify item
this.prepareUpdate(item);
if ( debug ){
console.debug( 'update repository: ' + item.name );
}

View File

@@ -432,21 +432,22 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
var infoPanel = main.getInfoPanel(item.type);
infoPanel.item = item;
var settingsForm = main.getSettingsForm(item.type);
settingsForm.item = item;
settingsForm.onUpdate = {
fn: this.reload,
scope: this
};
settingsForm.onCreate = {
fn: this.reload,
scope: this
};
var panels = [infoPanel];
if ( owner ){
panels.push({
item: item,
xtype: 'repositorySettingsForm',
onUpdate: {
fn: this.reload,
scope: this
},
onCreate: {
fn: this.reload,
scope: this
}
},{
panels.push(
settingsForm, {
item: item,
xtype: 'repositoryPermissionsForm',
listeners: {

View File

@@ -159,26 +159,10 @@ Sonia.repository.RepositoryBrowser = Ext.extend(Ext.grid.GridPanel, {
branches = true;
var branchStore = new Sonia.rest.JsonStore({
proxy: new Ext.data.HttpProxy({
url: restUrl + 'repositories/' + this.repository.id + '/branches.json',
method: 'GET',
disableCaching: false
}),
root: 'branch',
idProperty: 'name',
fields: [ 'name', 'revision' ]
});
items.push('->','Branches:', ' ',{
id: 'branchComboBox',
xtype: 'combo',
valueField: 'revision',
displayField: 'name',
typeAhead: false,
editable: false,
triggerAction: 'all',
store: branchStore,
xtype: 'repositoryBranchComboBox',
repositoryId: this.repository.id,
listeners: {
select: {
fn: this.selectBranch,
@@ -191,17 +175,6 @@ Sonia.repository.RepositoryBrowser = Ext.extend(Ext.grid.GridPanel, {
if ( type && type.supportedCommands && type.supportedCommands.indexOf('TAGS') >= 0){
var tagStore = new Sonia.rest.JsonStore({
proxy: new Ext.data.HttpProxy({
url: restUrl + 'repositories/' + this.repository.id + '/tags.json',
method: 'GET',
disableCaching: false
}),
root: 'tag',
idProperty: 'name',
fields: [ 'name', 'revision' ]
});
if (branches){
items.push(' ');
} else {
@@ -210,13 +183,8 @@ Sonia.repository.RepositoryBrowser = Ext.extend(Ext.grid.GridPanel, {
items.push('Tags:', ' ',{
id: 'tagComboBox',
xtype: 'combo',
valueField: 'revision',
displayField: 'name',
typeAhead: false,
editable: false,
triggerAction: 'all',
store: tagStore,
xtype: 'repositoryTagComboBox',
repositoryId: this.repository.id,
listeners: {
select: {
fn: this.selectTag,

View File

@@ -78,9 +78,15 @@ Sonia.repository.SettingsFormPanel = Ext.extend(Sonia.repository.FormPanel, {
helpText: this.publicHelpText
}]
};
this.modifyDefaultConfig(config);
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.repository.SettingsFormPanel.superclass.initComponent.apply(this, arguments);
},
modifyDefaultConfig: function(config){
}
});

View File

@@ -0,0 +1,65 @@
/* *
* Copyright (c) 2014, 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
*
*/
Sonia.repository.TagComboBox = Ext.extend(Ext.form.ComboBox, {
repositoryId: null,
initComponent: function(){
var tagStore = new Sonia.rest.JsonStore({
proxy: new Ext.data.HttpProxy({
url: restUrl + 'repositories/' + this.repositoryId + '/tags.json',
method: 'GET',
disableCaching: false
}),
root: 'tag',
idProperty: 'name',
fields: [ 'name', 'revision' ]
});
var config = {
valueField: 'revision',
displayField: 'name',
typeAhead: false,
editable: false,
triggerAction: 'all',
store: tagStore
};
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.repository.TagComboBox.superclass.initComponent.apply(this, arguments);
}
});
// register xtype
Ext.reg('repositoryTagComboBox', Sonia.repository.TagComboBox);

View File

@@ -78,6 +78,7 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
mainTabPanel: null,
infoPanels: [],
settingsForm: [],
scripts: [],
stylesheets: [],
@@ -96,6 +97,23 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
this.infoPanels[type] = panel;
},
registerSettingsForm: function(type, form){
this.settingsForm[type] = form;
},
getSettingsForm: function(type){
var rp = null;
var panel = this.settingsForm[type];
if ( ! panel ){
rp = {
xtype: 'repositorySettingsForm'
};
} else {
rp = Sonia.util.clone( panel );
}
return rp;
},
getInfoPanel: function(type){
var rp = null;
var panel = this.infoPanels[type];