From 4ec7006108074d821848e5c94e518db2a2cf138a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 25 Mar 2019 11:28:29 +0100 Subject: [PATCH 01/31] Enhance branch object with default flag --- .../java/sonia/scm/repository/Branch.java | 19 +++++++++++++++++-- .../repository/spi/GitBranchesCommand.java | 2 +- .../client/spi/GitBranchCommand.java | 2 +- .../scm/repository/spi/HgBranchesCommand.java | 2 +- .../client/spi/HgBranchCommand.java | 2 +- .../sonia/scm/api/v2/resources/BranchDto.java | 1 + .../DefaultChangesetToChangesetDtoMapper.java | 2 +- .../v2/resources/BranchRootResourceTest.java | 4 ++-- .../BranchToBranchDtoMapperTest.java | 2 +- 9 files changed, 26 insertions(+), 10 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/Branch.java b/scm-core/src/main/java/sonia/scm/repository/Branch.java index ce1d43c82b..d8950c80ac 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Branch.java +++ b/scm-core/src/main/java/sonia/scm/repository/Branch.java @@ -66,7 +66,7 @@ public final class Branch implements Serializable * This constructor should only be called from JAXB. * */ - public Branch() {} + Branch() {} /** * Constructs a new branch. @@ -75,10 +75,19 @@ public final class Branch implements Serializable * @param name name of the branch * @param revision latest revision of the branch */ - public Branch(String name, String revision) + Branch(String name, String revision, boolean defaultBranch) { this.name = name; this.revision = revision; + this.defaultBranch = defaultBranch; + } + + public static Branch normalBranch(String name, String revision) { + return new Branch(name, revision, false); + } + + public static Branch defaultBranch(String name, String revision) { + return new Branch(name, revision, true); } //~--- methods -------------------------------------------------------------- @@ -162,6 +171,10 @@ public final class Branch implements Serializable return revision; } + public boolean isDefaultBranch() { + return defaultBranch; + } + //~--- fields --------------------------------------------------------------- /** name of the branch */ @@ -169,4 +182,6 @@ public final class Branch implements Serializable /** Field description */ private String revision; + + private boolean defaultBranch; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java index 4922752b6f..110e02d431 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java @@ -92,7 +92,7 @@ public class GitBranchesCommand extends AbstractGitCommand if (branchName != null) { - branch = new Branch(branchName, GitUtil.getId(ref.getObjectId())); + branch = Branch.normalBranch(branchName, GitUtil.getId(ref.getObjectId())); } return branch; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitBranchCommand.java index 5a721a7aa5..19197d009d 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitBranchCommand.java @@ -72,7 +72,7 @@ public class GitBranchCommand implements BranchCommand try { Ref ref = git.branchCreate().setName(name).call(); - return new Branch(name, GitUtil.getId(ref.getObjectId())); + return Branch.normalBranch(name, GitUtil.getId(ref.getObjectId())); } catch (GitAPIException ex) { diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java index 5c38205393..4ac4218168 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java @@ -88,7 +88,7 @@ public class HgBranchesCommand extends AbstractCommand node = changeset.getNode(); } - return new Branch(hgBranch.getName(), node); + return Branch.normalBranch(hgBranch.getName(), node); } }); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgBranchCommand.java index 74d1b1f742..fa7371f840 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgBranchCommand.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/client/spi/HgBranchCommand.java @@ -53,7 +53,7 @@ public class HgBranchCommand implements BranchCommand public Branch branch(String name) throws IOException { com.aragost.javahg.commands.BranchCommand.on(repository).set(name); - return new Branch(name, repository.tip().getNode()); + return Branch.normalBranch(name, repository.tip().getNode()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java index 343d9c8bc8..c27baf3666 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java @@ -12,6 +12,7 @@ public class BranchDto extends HalRepresentation { private String name; private String revision; + private boolean defaultBranch; BranchDto(Links links, Embedded embedded) { super(links, embedded); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java index 479a43aef1..1ae67c4282 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java @@ -60,7 +60,7 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMa } if (repositoryService.isSupported(Command.BRANCHES)) { embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name, - getListOfObjects(source.getBranches(), branchName -> new Branch(branchName, source.getId())))); + getListOfObjects(source.getBranches(), branchName -> Branch.normalBranch(branchName, source.getId())))); } } embeddedBuilder.with("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository))); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java index 9216922e19..d432dee18f 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java @@ -125,7 +125,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { @Test public void shouldFindExistingBranch() throws Exception { - when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(new Branch("master", "revision"))); + when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(Branch.normalBranch("master", "revision"))); MockHttpRequest request = MockHttpRequest.get(BRANCH_URL); MockHttpResponse response = new MockHttpResponse(); @@ -153,7 +153,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { when(logCommandBuilder.setBranch(anyString())).thenReturn(logCommandBuilder); when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult); Branches branches = mock(Branches.class); - List branchList = Lists.newArrayList(new Branch("master",id)); + List branchList = Lists.newArrayList(Branch.normalBranch("master",id)); when(branches.getBranches()).thenReturn(branchList); when(branchesCommandBuilder.getBranches()).thenReturn(branches); MockHttpRequest request = MockHttpRequest.get(BRANCH_URL + "/changesets/"); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java index 3e64ab95b6..9c15271aa2 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java @@ -33,7 +33,7 @@ class BranchToBranchDtoMapperTest { }); mapper.setRegistry(registry); - Branch branch = new Branch("master", "42"); + Branch branch = Branch.normalBranch("master", "42"); BranchDto dto = mapper.map(branch, new NamespaceAndName("hitchhiker", "heart-of-gold")); assertThat(dto.getLinks().getLinkBy("ka").get().getHref()).isEqualTo("http://hitchhiker/heart-of-gold/master"); From b36f1ca3e4c94ac3632a2b96b09bde777c716e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 25 Mar 2019 12:08:44 +0100 Subject: [PATCH 02/31] Mark default branch for git --- .../java/sonia/scm/repository/Branch.java | 3 +- .../repository/spi/GitBranchesCommand.java | 95 ++++++++------ .../spi/GitBranchesCommandTest.java | 118 ++++++++++++++++++ 3 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Branch.java b/scm-core/src/main/java/sonia/scm/repository/Branch.java index d8950c80ac..c0a7289912 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Branch.java +++ b/scm-core/src/main/java/sonia/scm/repository/Branch.java @@ -116,7 +116,8 @@ public final class Branch implements Serializable final Branch other = (Branch) obj; return Objects.equal(name, other.name) - && Objects.equal(revision, other.revision); + && Objects.equal(revision, other.revision) + && Objects.equal(defaultBranch, other.defaultBranch); } /** diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java index 110e02d431..81b38cab5c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java @@ -34,11 +34,15 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.base.Function; -import com.google.common.collect.Lists; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.value.qual.UnknownVal; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; @@ -46,6 +50,8 @@ import sonia.scm.repository.Repository; import java.io.IOException; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; //~--- JDK imports ------------------------------------------------------------ @@ -53,17 +59,10 @@ import java.util.List; * * @author Sebastian Sdorra */ -public class GitBranchesCommand extends AbstractGitCommand - implements BranchesCommand -{ +public class GitBranchesCommand extends AbstractGitCommand implements BranchesCommand { + + private static final Logger LOG = LoggerFactory.getLogger(GitBranchesCommand.class); - /** - * Constructs ... - * - * - * @param context - * @param repository - */ public GitBranchesCommand(GitContext context, Repository repository) { super(context, repository); @@ -73,38 +72,54 @@ public class GitBranchesCommand extends AbstractGitCommand @Override public List getBranches() throws IOException { - List branches = null; + Git git = createGit(); - Git git = new Git(open()); + String defaultBranchName = determineDefaultBranchName(git); - try - { - List refs = git.branchList().call(); - - branches = Lists.transform(refs, new Function() - { - - @Override - public Branch apply(Ref ref) - { - Branch branch = null; - String branchName = GitUtil.getBranch(ref); - - if (branchName != null) - { - branch = Branch.normalBranch(branchName, GitUtil.getId(ref.getObjectId())); - } - - return branch; - } - }); - - } - catch (GitAPIException ex) - { + try { + return git + .branchList() + .call() + .stream() + .map(ref -> createBranchObject(defaultBranchName, ref)) + .collect(Collectors.toList()); + } catch (GitAPIException ex) { throw new InternalRepositoryException(repository, "could not read branches", ex); } + } - return branches; + @VisibleForTesting + Git createGit() throws IOException { + return new Git(open()); + } + + @Nullable + private Branch createBranchObject(String defaultBranchName, Ref ref) { + String branchName = GitUtil.getBranch(ref); + + if (branchName == null) { + LOG.warn("could not determine branch name for branch name {} at revision {}", ref.getName(), ref.getObjectId()); + return null; + } else { + if (branchName.equals(defaultBranchName)) { + return Branch.defaultBranch(branchName, GitUtil.getId(ref.getObjectId())); + } else { + return Branch.normalBranch(branchName, GitUtil.getId(ref.getObjectId())); + } + } + } + + private String determineDefaultBranchName(Git git) { + String defaultBranchName = context.getConfig().getDefaultBranch(); + if (Strings.isNullOrEmpty(defaultBranchName)) { + return getRepositoryHeadRef(git).map(GitUtil::getBranch).orElse(null); + } else { + return defaultBranchName; + } + } + + @UnknownVal + Optional getRepositoryHeadRef(Git git) { + return GitUtil.getRepositoryHeadRef(git.getRepository()); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java new file mode 100644 index 0000000000..af765bc7aa --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java @@ -0,0 +1,118 @@ +package sonia.scm.repository.spi; + +import org.checkerframework.common.value.qual.UnknownVal; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ListBranchCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.repository.Branch; +import sonia.scm.repository.GitRepositoryConfig; +import sonia.scm.repository.Repository; + +import java.io.IOException; +import java.util.List; +import java.util.Optional; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Optional.of; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GitBranchesCommandTest { + + @Mock + GitContext context; + @Mock + Git git; + @Mock + ListBranchCommand listBranchCommand; + @Mock + GitRepositoryConfig gitRepositoryConfig; + + GitBranchesCommand branchesCommand; + private Ref master; + + @BeforeEach + void initContext() { + when(context.getConfig()).thenReturn(gitRepositoryConfig); + } + + @BeforeEach + void initCommand() { + master = createRef("master", "0000"); + branchesCommand = new GitBranchesCommand(context, new Repository("1", "git", "space", "X")) { + @Override + Git createGit() { + return git; + } + + @Override + @UnknownVal Optional getRepositoryHeadRef(Git git) { + return of(master); + } + }; + when(git.branchList()).thenReturn(listBranchCommand); + } + + @Test + void shouldCreateEmptyListWithoutBranches() throws IOException, GitAPIException { + when(listBranchCommand.call()).thenReturn(emptyList()); + + List branches = branchesCommand.getBranches(); + + assertThat(branches).isEmpty(); + } + + @Test + void shouldMapNormalBranch() throws IOException, GitAPIException { + Ref branch = createRef("branch", "1337"); + when(listBranchCommand.call()).thenReturn(asList(branch)); + + List branches = branchesCommand.getBranches(); + + assertThat(branches).containsExactly(Branch.normalBranch("branch", "1337")); + } + + @Test + void shouldMarkMasterBranchWithMasterFromConfig() throws IOException, GitAPIException { + Ref branch = createRef("branch", "1337"); + when(listBranchCommand.call()).thenReturn(asList(branch)); + when(gitRepositoryConfig.getDefaultBranch()).thenReturn("branch"); + + List branches = branchesCommand.getBranches(); + + assertThat(branches).containsExactlyInAnyOrder(Branch.defaultBranch("branch", "1337")); + } + + @Test + void shouldMarkMasterBranchWithMasterFromHead() throws IOException, GitAPIException { + Ref branch = createRef("branch", "1337"); + when(listBranchCommand.call()).thenReturn(asList(branch, master)); + + List branches = branchesCommand.getBranches(); + + assertThat(branches).containsExactlyInAnyOrder( + Branch.normalBranch("branch", "1337"), + Branch.defaultBranch("master", "0000") + ); + } + + private Ref createRef(String branchName, String revision) { + Ref ref = mock(Ref.class); + lenient().when(ref.getName()).thenReturn("refs/heads/" + branchName); + ObjectId objectId = mock(ObjectId.class); + lenient().when(objectId.name()).thenReturn(revision); + lenient().when(ref.getObjectId()).thenReturn(objectId); + return ref; + } +} From 56e61ab966bb5b4c562dd4746578c61699083696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 25 Mar 2019 14:20:23 +0100 Subject: [PATCH 03/31] Mark default branch for hg --- .../java/sonia/scm/repository/spi/HgBranchesCommand.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java index 4ac4218168..55b937be4b 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchesCommand.java @@ -53,6 +53,8 @@ public class HgBranchesCommand extends AbstractCommand implements BranchesCommand { + private static final String DEFAULT_BRANCH_NAME = "default"; + /** * Constructs ... * @@ -88,7 +90,11 @@ public class HgBranchesCommand extends AbstractCommand node = changeset.getNode(); } - return Branch.normalBranch(hgBranch.getName(), node); + if (DEFAULT_BRANCH_NAME.equals(hgBranch.getName())) { + return Branch.defaultBranch(hgBranch.getName(), node); + } else { + return Branch.normalBranch(hgBranch.getName(), node); + } } }); From 39ae41327a9f3e01c50dbff97be6cba22b7b2f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 25 Mar 2019 16:44:05 +0100 Subject: [PATCH 04/31] Add missing mockito config file --- .../resources/mockito-extensions/org.mockito.plugins.MockMaker | 1 + 1 file changed, 1 insertion(+) create mode 100644 scm-plugins/scm-git-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/scm-plugins/scm-git-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/scm-plugins/scm-git-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..1f0955d450 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline From 5ca4bf6b302e83256cf06de9452b70d2c1d229bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 29 Mar 2019 10:03:59 +0100 Subject: [PATCH 05/31] Create intermediate branch From b14eadb13d9fd8460acc84c52b6b6942f9d03e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 25 Mar 2019 16:44:05 +0100 Subject: [PATCH 06/31] Add missing mockito config file --- .../resources/mockito-extensions/org.mockito.plugins.MockMaker | 1 + 1 file changed, 1 insertion(+) create mode 100644 scm-plugins/scm-git-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/scm-plugins/scm-git-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/scm-plugins/scm-git-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..1f0955d450 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline From 3c7930d1a99dd16605f91155a6189eec0c891fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 26 Mar 2019 09:16:33 +0100 Subject: [PATCH 07/31] Add "production" branch command There already was a branch command for test purposes. This was adapted for production use with git and hg. --- .../repository/api/BranchCommandBuilder.java | 101 ++++++++++++++++++ .../sonia/scm/repository/api/Command.java | 5 + .../scm/repository/api/RepositoryService.java | 14 +++ .../scm/repository/spi/BranchCommand.java | 45 ++++++++ .../spi/RepositoryServiceProvider.java | 11 ++ .../scm/repository/spi/GitBranchCommand.java | 60 +++++++++++ .../spi/GitRepositoryServiceProvider.java | 12 +++ .../repository/spi/GitBranchCommandTest.java | 27 +++++ .../scm/repository/spi/HgBranchCommand.java | 68 ++++++++++++ .../spi/HgRepositoryServiceProvider.java | 6 ++ .../repository/spi/HgBranchCommandTest.java | 22 ++++ 11 files changed, 371 insertions(+) create mode 100644 scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java new file mode 100644 index 0000000000..b0c6b824ee --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java @@ -0,0 +1,101 @@ +/** + * 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.api; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.repository.spi.BranchCommand; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +/** + * + * @author Sebastian Sdorra + * @since 1.18 + */ +public final class BranchCommandBuilder +{ + + /** + * the logger for BranchCommandBuilder + */ + private static final Logger logger = + LoggerFactory.getLogger(BranchCommandBuilder.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param command + */ + public BranchCommandBuilder(BranchCommand command) + { + this.command = command; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param name + * + * @return + * + * @throws IOException + */ + public BranchCommandBuilder branch(String name) throws IOException + { + if (logger.isDebugEnabled()) + { + logger.debug("branch {}", name); + } + + command.branch(name); + + return this; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private BranchCommand command; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/Command.java b/scm-core/src/main/java/sonia/scm/repository/api/Command.java index fa09cea2cb..e380727769 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/Command.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/Command.java @@ -53,6 +53,11 @@ public enum Command */ BRANCHES, + /** + * @since 2.0 + */ + BRANCH, + /** * @since 1.31 */ diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java index ad53c3a8f7..d59362a7f3 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java @@ -165,6 +165,20 @@ public final class RepositoryService implements Closeable { provider.getBranchesCommand(), repository); } + /** + * The branch command creates new branches. + * + * @return instance of {@link BranchCommandBuilder} + * @throws CommandNotSupportedException if the command is not supported + * by the implementation of the repository service provider. + */ + public BranchCommandBuilder getBranchCommand() { + logger.debug("create branch command for repository {}", + repository.getNamespaceAndName()); + + return new BranchCommandBuilder(provider.getBranchCommand()); + } + /** * The browse command allows browsing of a repository. * diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java new file mode 100644 index 0000000000..cbce371f4a --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java @@ -0,0 +1,45 @@ +/** + * 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.spi; + + +import sonia.scm.repository.Branch; + +import java.io.IOException; + +/** + * @since 2.0 + */ +public interface BranchCommand { + Branch branch(String name) throws IOException; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java b/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java index 77201d1a72..a82eb7c30a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/RepositoryServiceProvider.java @@ -101,6 +101,17 @@ public abstract class RepositoryServiceProvider implements Closeable throw new CommandNotSupportedException(Command.BRANCHES); } + /** + * Method description + * + * + * @return + */ + public BranchCommand getBranchCommand() + { + throw new CommandNotSupportedException(Command.BRANCH); + } + /** * Method description * diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java new file mode 100644 index 0000000000..23be8883fa --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java @@ -0,0 +1,60 @@ +/** + * 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.spi; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Ref; +import sonia.scm.repository.Branch; +import sonia.scm.repository.GitUtil; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Repository; + +import java.io.IOException; + +public class GitBranchCommand extends AbstractGitCommand implements BranchCommand { + + GitBranchCommand(GitContext context, Repository repository) { + super(context, repository); + } + + @Override + public Branch branch(String name) throws IOException { + try (Git git = new Git(open())) { + Ref ref = git.branchCreate().setName(name).call(); + return Branch.normalBranch(name, GitUtil.getId(ref.getObjectId())); + } catch (GitAPIException ex) { + throw new InternalRepositoryException(repository, "could not create branch " + name, ex); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index ef3d96d5cb..898c5875d3 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -120,6 +120,18 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider return new GitBranchesCommand(context, repository); } + /** + * Method description + * + * + * @return + */ + @Override + public BranchCommand getBranchCommand() + { + return new GitBranchCommand(context, repository); + } + /** * Method description * diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java new file mode 100644 index 0000000000..d5dfc17698 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java @@ -0,0 +1,27 @@ +package sonia.scm.repository.spi; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import sonia.scm.repository.Branch; + +import java.io.IOException; +import java.util.List; + + +public class GitBranchCommandTest extends AbstractGitCommandTestBase { + + @Test + public void shouldCreateBranch() throws IOException { + GitContext context = createContext(); + + Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); + + new GitBranchCommand(context, repository).branch("new_branch"); + + Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); + } + + private List readBranches(GitContext context) throws IOException { + return new GitBranchesCommand(context, repository).getBranches(); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java new file mode 100644 index 0000000000..01c5c5fc98 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java @@ -0,0 +1,68 @@ +/** + * 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.spi; + +import com.aragost.javahg.Changeset; +import com.aragost.javahg.commands.CommitCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.Branch; +import sonia.scm.repository.Repository; + +/** + * Mercurial implementation of the {@link BranchCommand}. + * Note that this creates an empty commit to "persist" the new branch. + */ +public class HgBranchCommand extends AbstractCommand implements BranchCommand { + + private static final Logger LOG = LoggerFactory.getLogger(HgBranchCommand.class); + + HgBranchCommand(HgCommandContext context, Repository repository) { + super(context, repository); + } + + @Override + public Branch branch(String name) { + com.aragost.javahg.Repository repository = open(); + com.aragost.javahg.commands.BranchCommand.on(repository).set(name); + + Changeset emptyChangeset = CommitCommand + .on(repository) + .user("SCM-Manager") + .message("Create new branch " + name) + .execute(); + + LOG.debug("Created new branch '{}' in repository {} with changeset {}", + name, getRepository().getNamespaceAndName(), emptyChangeset.getNode()); + + return Branch.normalBranch(name, emptyChangeset.getNode()); + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java index 2c18aea2c3..0b88c07cb1 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java @@ -126,6 +126,11 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider return new HgBranchesCommand(context, repository); } + @Override + public BranchCommand getBranchCommand() { + return new HgBranchCommand(context, repository); + } + /** * Method description * @@ -192,6 +197,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider * @return the corresponding {@link ModificationsCommand} implemented from the Plugins * @throws CommandNotSupportedException if there is no Implementation */ + @Override public ModificationsCommand getModificationsCommand() { return new HgModificationsCommand(context,repository); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java new file mode 100644 index 0000000000..8c0b1862d1 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -0,0 +1,22 @@ +package sonia.scm.repository.spi; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import sonia.scm.repository.Branch; + +import java.util.List; + +public class HgBranchCommandTest extends AbstractHgCommandTestBase { + @Test + public void x() { + Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); + + new HgBranchCommand(cmdContext, repository).branch("new_branch"); + + Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); + } + + private List readBranches() { + return new HgBranchesCommand(cmdContext, repository).getBranches(); + } +} From 744af9bcfc97d97c7d04a1480f01c404728994f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 26 Mar 2019 10:04:39 +0100 Subject: [PATCH 08/31] Fix branch command builder --- .../repository/api/BranchCommandBuilder.java | 57 +++---------------- 1 file changed, 8 insertions(+), 49 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java index b0c6b824ee..09860d1514 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java @@ -32,70 +32,29 @@ package sonia.scm.repository.api; -//~--- non-JDK imports -------------------------------------------------------- - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - +import sonia.scm.repository.Branch; import sonia.scm.repository.spi.BranchCommand; -//~--- JDK imports ------------------------------------------------------------ - import java.io.IOException; /** - * - * @author Sebastian Sdorra - * @since 1.18 + * @since 2.0 */ -public final class BranchCommandBuilder -{ +public final class BranchCommandBuilder { - /** - * the logger for BranchCommandBuilder - */ - private static final Logger logger = - LoggerFactory.getLogger(BranchCommandBuilder.class); + private static final Logger LOG = LoggerFactory.getLogger(BranchCommandBuilder.class); - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param command - */ - public BranchCommandBuilder(BranchCommand command) - { + public BranchCommandBuilder(BranchCommand command) { this.command = command; } - //~--- methods -------------------------------------------------------------- + public Branch branch(String name) throws IOException { + LOG.debug("branch {}", name); - /** - * Method description - * - * - * @param name - * - * @return - * - * @throws IOException - */ - public BranchCommandBuilder branch(String name) throws IOException - { - if (logger.isDebugEnabled()) - { - logger.debug("branch {}", name); - } - - command.branch(name); - - return this; + return command.branch(name); } - //~--- fields --------------------------------------------------------------- - - /** Field description */ private BranchCommand command; } From 58b7dff63159bfdda6e62006089a3655614e1d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 26 Mar 2019 10:55:39 +0100 Subject: [PATCH 09/31] Create REST endpoint for branch creation --- .../BranchCollectionToDtoMapper.java | 27 ++++++-- .../sonia/scm/api/v2/resources/BranchDto.java | 9 +++ .../api/v2/resources/BranchRootResource.java | 68 ++++++++++++++++--- .../scm/api/v2/resources/ResourceLinks.java | 3 + .../scm/api/v2/resources/BranchDtoTest.java | 51 ++++++++++++++ .../v2/resources/BranchRootResourceTest.java | 61 +++++++++++++++-- 6 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDtoTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionToDtoMapper.java index 51ce9e6f5a..b394c4215f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionToDtoMapper.java @@ -3,9 +3,12 @@ package sonia.scm.api.v2.resources; import com.google.inject.Inject; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Link; import de.otto.edison.hal.Links; import sonia.scm.repository.Branch; import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermissions; import java.util.Collection; import java.util.List; @@ -25,22 +28,36 @@ public class BranchCollectionToDtoMapper { this.branchToDtoMapper = branchToDtoMapper; } - public HalRepresentation map(String namespace, String name, Collection branches) { - return new HalRepresentation(createLinks(namespace, name), embedDtos(getBranchDtoList(namespace, name, branches))); + public HalRepresentation map(Repository repository, Collection branches) { + return new HalRepresentation( + createLinks(repository), + embedDtos(getBranchDtoList(repository.getNamespace(), repository.getName(), branches))); } public List getBranchDtoList(String namespace, String name, Collection branches) { return branches.stream().map(branch -> branchToDtoMapper.map(branch, new NamespaceAndName(namespace, name))).collect(toList()); } - private Links createLinks(String namespace, String name) { + private Links createLinks(Repository repository) { + String namespace = repository.getNamespace(); + String name = repository.getName(); String baseUrl = resourceLinks.branchCollection().self(namespace, name); - Links.Builder linksBuilder = linkingTo() - .with(Links.linkingTo().self(baseUrl).build()); + Links.Builder linksBuilder = linkingTo().with(createSelfLink(baseUrl)); + if (RepositoryPermissions.push(repository).isPermitted()) { + linksBuilder.single(createCreateLink(namespace, name)); + } return linksBuilder.build(); } + private Links createSelfLink(String baseUrl) { + return Links.linkingTo().self(baseUrl).build(); + } + + private Link createCreateLink(String namespace, String name) { + return Link.link("create", resourceLinks.branch().create(namespace, name)); + } + private Embedded embedDtos(List dtos) { return embeddedBuilder() .with("branches", dtos) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java index c27baf3666..c66428697d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java @@ -6,10 +6,19 @@ import de.otto.edison.hal.Links; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.constraints.Pattern; @Getter @Setter @NoArgsConstructor public class BranchDto extends HalRepresentation { + private static final String VALID_CHARACTERS_AT_START_AND_END = "\\w-,;\\]{}@&+=$#`|<>"; + private static final String VALID_CHARACTERS = VALID_CHARACTERS_AT_START_AND_END + "/."; + static final String VALID_BRANCH_NAMES = "[" + VALID_CHARACTERS_AT_START_AND_END + "]([" + VALID_CHARACTERS + "]*[" + VALID_CHARACTERS_AT_START_AND_END + "])?"; + + @NotEmpty @Length(min = 1, max=100) @Pattern(regexp = VALID_BRANCH_NAMES) private String name; private String revision; private boolean defaultBranch; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java index d19c1a9e03..b35b59beca 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java @@ -1,9 +1,10 @@ package sonia.scm.api.v2.resources; import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.ResponseHeader; +import com.webcohesion.enunciate.metadata.rs.ResponseHeaders; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; -import sonia.scm.NotFoundException; import sonia.scm.PageResult; import sonia.scm.repository.Branch; import sonia.scm.repository.Branches; @@ -18,15 +19,20 @@ import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.validation.Valid; +import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import java.io.IOException; +import java.net.URI; +import static sonia.scm.AlreadyExistsException.alreadyExists; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; @@ -38,12 +44,15 @@ public class BranchRootResource { private final BranchChangesetCollectionToDtoMapper branchChangesetCollectionToDtoMapper; + private final ResourceLinks resourceLinks; + @Inject - public BranchRootResource(RepositoryServiceFactory serviceFactory, BranchToBranchDtoMapper branchToDtoMapper, BranchCollectionToDtoMapper branchCollectionToDtoMapper, BranchChangesetCollectionToDtoMapper changesetCollectionToDtoMapper) { + public BranchRootResource(RepositoryServiceFactory serviceFactory, BranchToBranchDtoMapper branchToDtoMapper, BranchCollectionToDtoMapper branchCollectionToDtoMapper, BranchChangesetCollectionToDtoMapper changesetCollectionToDtoMapper, ResourceLinks resourceLinks) { this.serviceFactory = serviceFactory; this.branchToDtoMapper = branchToDtoMapper; this.branchCollectionToDtoMapper = branchCollectionToDtoMapper; this.branchChangesetCollectionToDtoMapper = changesetCollectionToDtoMapper; + this.resourceLinks = resourceLinks; } /** @@ -100,12 +109,7 @@ public class BranchRootResource { @DefaultValue("0") @QueryParam("page") int page, @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { - boolean branchExists = repositoryService.getBranchesCommand() - .getBranches() - .getBranches() - .stream() - .anyMatch(branch -> branchName.equals(branch.getName())); - if (!branchExists){ + if (!branchExists(branchName, repositoryService)){ throw notFound(entity(Branch.class, branchName).in(Repository.class, namespace + "/" + name)); } Repository repository = repositoryService.getRepository(); @@ -125,6 +129,50 @@ public class BranchRootResource { } } + /** + * Creates a new branch. + * + * @param namespace the namespace of the repository + * @param name the name of the repository + * @param branchName The branch to be created. + * @return A response with the link to the new branch (if created successfully). + */ + @POST + @Path("") + @Consumes(VndMediaType.BRANCH) + @StatusCodes({ + @ResponseCode(code = 201, condition = "create success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"push\" privilege"), + @ResponseCode(code = 409, condition = "conflict, a user with this name already exists"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created branch")) + public Response create(@PathParam("namespace") String namespace, + @PathParam("name") String name, + @Valid BranchDto branchToCreate) throws IOException { + NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); + String branchName = branchToCreate.getName(); + try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) { + if (branchExists(branchName, repositoryService)) { + throw alreadyExists(entity(Branch.class, branchName).in(Repository.class, namespace + "/" + name)); + } + Repository repository = repositoryService.getRepository(); + RepositoryPermissions.push(repository).check(); + Branch newBranch = repositoryService.getBranchCommand().branch(branchName); + return Response.created(URI.create(resourceLinks.branch().self(namespaceAndName, newBranch.getName()))).build(); + } + } + + private boolean branchExists(String branchName, RepositoryService repositoryService) throws IOException { + return repositoryService.getBranchesCommand() + .getBranches() + .getBranches() + .stream() + .anyMatch(branch -> branchName.equals(branch.getName())); + } + /** * Returns the branches for a repository. * @@ -141,14 +189,14 @@ public class BranchRootResource { @ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 400, condition = "branches not supported for given repository"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), - @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"read repository\" privilege"), @ResponseCode(code = 404, condition = "not found, no repository found for the given namespace and name"), @ResponseCode(code = 500, condition = "internal server error") }) public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Branches branches = repositoryService.getBranchesCommand().getBranches(); - return Response.ok(branchCollectionToDtoMapper.map(namespace, name, branches.getBranches())).build(); + return Response.ok(branchCollectionToDtoMapper.map(repositoryService.getRepository(), branches.getBranches())).build(); } catch (CommandNotSupportedException ex) { return Response.status(Response.Status.BAD_REQUEST).build(); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index 1fc6b2a442..ff1013bb76 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -386,6 +386,9 @@ class ResourceLinks { return branchLinkBuilder.method("getRepositoryResource").parameters(namespaceAndName.getNamespace(), namespaceAndName.getName()).method("branches").parameters().method("history").parameters(branch).href(); } + public String create(String namespace, String name) { + return branchLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("branches").parameters().method("create").parameters().href(); + } } public IncomingLinks incoming() { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDtoTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDtoTest.java new file mode 100644 index 0000000000..b9072e3167 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDtoTest.java @@ -0,0 +1,51 @@ +package sonia.scm.api.v2.resources; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BranchDtoTest { + + @ParameterizedTest + @ValueSource(strings = { + "v", + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", + "val#x", + "val&x", + "val+", + "val,kill", + "val.kill", + "val;kill", + "valkill", + "val@", + "val]id", + "val`id", + "valid#", + "valid.t", + "val{", + "val{d", + "val{}d", + "val|kill", + "val}" + }) + void shouldAcceptValidBranchName(String branchName) { + assertTrue(branchName.matches(BranchDto.VALID_BRANCH_NAMES)); + } + + @ParameterizedTest + @ValueSource(strings = { + "", + ".val", + "val.", + "/val", + "val/", + "val id" + }) + void shouldRejectInvalidBranchName(String branchName) { + assertFalse(branchName.matches(BranchDto.VALID_BRANCH_NAMES)); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java index d432dee18f..eae9d36082 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java @@ -25,10 +25,12 @@ import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Person; import sonia.scm.repository.Repository; +import sonia.scm.repository.api.BranchCommandBuilder; import sonia.scm.repository.api.BranchesCommandBuilder; import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.web.VndMediaType; import java.net.URI; import java.time.Instant; @@ -49,6 +51,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { public static final String BRANCH_PATH = "space/repo/branches/master"; public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH; + public static final String REVISION = "revision"; private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); private final URI baseUri = URI.create("/"); @@ -60,6 +63,8 @@ public class BranchRootResourceTest extends RepositoryTestBase { private RepositoryService service; @Mock private BranchesCommandBuilder branchesCommandBuilder; + @Mock + private BranchCommandBuilder branchCommandBuilder; @Mock private LogCommandBuilder logCommandBuilder; @@ -89,10 +94,10 @@ public class BranchRootResourceTest extends RepositoryTestBase { @Before - public void prepareEnvironment() throws Exception { + public void prepareEnvironment() { changesetCollectionToDtoMapper = new BranchChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks); - branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper); + branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper, resourceLinks); super.branchRootResource = Providers.of(branchRootResource); dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); @@ -100,6 +105,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); when(service.getBranchesCommand()).thenReturn(branchesCommandBuilder); + when(service.getBranchCommand()).thenReturn(branchCommandBuilder); when(service.getLogCommand()).thenReturn(logCommandBuilder); subjectThreadState.bind(); ThreadContext.bind(subject); @@ -125,7 +131,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { @Test public void shouldFindExistingBranch() throws Exception { - when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(Branch.normalBranch("master", "revision"))); + when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(createBranch("master"))); MockHttpRequest request = MockHttpRequest.get(BRANCH_URL); MockHttpResponse response = new MockHttpResponse(); @@ -139,13 +145,12 @@ public class BranchRootResourceTest extends RepositoryTestBase { @Test public void shouldFindHistory() throws Exception { - String id = "revision_123"; Instant creationDate = Instant.now(); String authorName = "name"; String authorEmail = "em@i.l"; String commit = "my branch commit"; ChangesetPagingResult changesetPagingResult = mock(ChangesetPagingResult.class); - List changesetList = Lists.newArrayList(new Changeset(id, Date.from(creationDate).getTime(), new Person(authorName, authorEmail), commit)); + List changesetList = Lists.newArrayList(new Changeset(REVISION, Date.from(creationDate).getTime(), new Person(authorName, authorEmail), commit)); when(changesetPagingResult.getChangesets()).thenReturn(changesetList); when(changesetPagingResult.getTotal()).thenReturn(1); when(logCommandBuilder.setPagingStart(anyInt())).thenReturn(logCommandBuilder); @@ -153,7 +158,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { when(logCommandBuilder.setBranch(anyString())).thenReturn(logCommandBuilder); when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult); Branches branches = mock(Branches.class); - List branchList = Lists.newArrayList(Branch.normalBranch("master",id)); + List branchList = Lists.newArrayList(createBranch("master")); when(branches.getBranches()).thenReturn(branchList); when(branchesCommandBuilder.getBranches()).thenReturn(branches); MockHttpRequest request = MockHttpRequest.get(BRANCH_URL + "/changesets/"); @@ -161,9 +166,51 @@ public class BranchRootResourceTest extends RepositoryTestBase { dispatcher.invoke(request, response); assertEquals(200, response.getStatus()); log.info("Response :{}", response.getContentAsString()); - assertTrue(response.getContentAsString().contains(String.format("\"id\":\"%s\"", id))); + assertTrue(response.getContentAsString().contains(String.format("\"id\":\"%s\"", REVISION))); assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", authorName))); assertTrue(response.getContentAsString().contains(String.format("\"mail\":\"%s\"", authorEmail))); assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); } + + @Test + public void shouldCreateNewBranch() throws Exception { + when(branchesCommandBuilder.getBranches()).thenReturn(new Branches()); + when(branchCommandBuilder.branch("new_branch")).thenReturn(createBranch("new_branch")); + + MockHttpRequest request = MockHttpRequest + .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/") + .content("{\"name\": \"new_branch\"}".getBytes()) + .contentType(VndMediaType.BRANCH); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(201, response.getStatus()); + assertEquals( + URI.create("/v2/repositories/space/repo/branches/new_branch"), + response.getOutputHeaders().getFirst("Location")); + } + + @Test + public void shouldNotCreateExistingBranchAgain() throws Exception { + when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(createBranch("existing_branch"))); + when(branchCommandBuilder.branch("new_branch")).thenReturn(createBranch("new_branch")); + + MockHttpRequest request = MockHttpRequest + .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/") + .content("{\"name\": \"new_branch\"}".getBytes()) + .contentType(VndMediaType.BRANCH); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(201, response.getStatus()); + assertEquals( + URI.create("/v2/repositories/space/repo/branches/new_branch"), + response.getOutputHeaders().getFirst("Location")); + } + + private Branch createBranch(String existing_branch) { + return Branch.normalBranch(existing_branch, REVISION); + } } From b65e84249d46a7d07bafe33c2d6d95b56b3ba831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 27 Mar 2019 10:08:20 +0100 Subject: [PATCH 10/31] Use clone and push to create branches Generalize workdir creation for git and hg and create branches in clones instead of the scm repository, so that hooks will be fired correctly once the changes are pushed back. Missing: - Evaluation of the git response from the push command - configuration of the hg environment and the authentication, so that the scmhooks.py script can be triggeret correctly and can callback the scm manager --- .../repository/util}/CloseableWrapper.java | 12 ++-- .../repository/util/SimpleWorkdirFactory.java | 57 ++++++++++++++++++ .../scm/repository/util/WorkdirFactory.java | 5 ++ .../scm/repository/util/WorkingCopy.java | 31 ++++++++++ .../scm/repository/GitWorkdirFactory.java | 6 +- .../scm/repository/spi/GitBranchCommand.java | 28 +++++++-- .../scm/repository/spi/GitMergeCommand.java | 3 +- .../spi/GitRepositoryServiceProvider.java | 2 +- .../spi/SimpleGitWorkdirFactory.java | 59 +++++++------------ .../sonia/scm/repository/spi/WorkingCopy.java | 12 ---- .../scm/repository/CloseableWrapperTest.java | 1 + .../spi/AbstractGitCommandTestBase.java | 9 +++ .../spi/BindTransportProtocolRule.java | 38 ++++++++++++ .../repository/spi/GitBranchCommandTest.java | 6 +- .../repository/spi/GitMergeCommandTest.java | 23 +------- .../spi/SimpleGitWorkdirFactoryTest.java | 33 ++--------- .../scm/repository/HgRepositoryHandler.java | 10 +++- .../scm/repository/spi/HgBranchCommand.java | 41 +++++++++---- .../spi/HgRepositoryServiceProvider.java | 2 +- .../scm/repository/spi/HgWorkdirFactory.java | 6 ++ .../spi/SimpleHgWorkdirFactory.java | 43 ++++++++++++++ .../java/sonia/scm/web/HgServletModule.java | 4 ++ .../repository/HgRepositoryHandlerTest.java | 4 +- .../java/sonia/scm/repository/HgTestUtil.java | 2 +- .../repository/spi/HgBranchCommandTest.java | 5 +- 25 files changed, 306 insertions(+), 136 deletions(-) rename {scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository => scm-core/src/main/java/sonia/scm/repository/util}/CloseableWrapper.java (50%) create mode 100644 scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java delete mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/WorkingCopy.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/CloseableWrapper.java b/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java similarity index 50% rename from scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/CloseableWrapper.java rename to scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java index 553f0f5a00..17980ced36 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/CloseableWrapper.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java @@ -1,18 +1,18 @@ -package sonia.scm.repository; +package sonia.scm.repository.util; import java.util.function.Consumer; -public class CloseableWrapper implements AutoCloseable { +public class CloseableWrapper implements AutoCloseable { - private final C wrapped; - private final Consumer cleanup; + private final T wrapped; + private final Consumer cleanup; - public CloseableWrapper(C wrapped, Consumer cleanup) { + public CloseableWrapper(T wrapped, Consumer cleanup) { this.wrapped = wrapped; this.cleanup = cleanup; } - public C get() { return wrapped; } + public T get() { return wrapped; } @Override public void close() { diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java new file mode 100644 index 0000000000..f2d436dffd --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -0,0 +1,57 @@ +package sonia.scm.repository.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Repository; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class SimpleWorkdirFactory { + + private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class); + + private final File poolDirectory; + + private final CloneProvider cloneProvider; + private final Repository repository; + + public SimpleWorkdirFactory(Repository repository, CloneProvider cloneProvider) { + this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool"), repository, cloneProvider); + } + + public SimpleWorkdirFactory(File poolDirectory, Repository repository, CloneProvider cloneProvider) { + this.poolDirectory = poolDirectory; + this.cloneProvider = cloneProvider; + this.repository = repository; + poolDirectory.mkdirs(); + } + + public WorkingCopy createWorkingCopy(C context) { + try { + File directory = createNewWorkdir(); + T clone = cloneProvider.cloneRepository(context, directory); + return new WorkingCopy<>(clone, this::close, directory); + } catch (IOException e) { + throw new InternalRepositoryException(repository, "could not create temporary directory for clone of repository", e); + } + } + + private File createNewWorkdir() throws IOException { + return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); + } + + private void close(T repository) { + try { + repository.close(); + } catch (Exception e) { + logger.warn("could not close temporary repository clone", e); + } + } + + public interface CloneProvider { + T cloneRepository(C context, File target) throws IOException; + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java new file mode 100644 index 0000000000..1d3878b250 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java @@ -0,0 +1,5 @@ +package sonia.scm.repository.util; + +public interface WorkdirFactory { + WorkingCopy createWorkingCopy(C gitContext); +} diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java new file mode 100644 index 0000000000..6271b8e199 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java @@ -0,0 +1,31 @@ +package sonia.scm.repository.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.util.IOUtil; + +import java.io.File; +import java.io.IOException; +import java.util.function.Consumer; + +public class WorkingCopy extends CloseableWrapper { + + private static final Logger LOG = LoggerFactory.getLogger(WorkingCopy.class); + + private final File directory; + + public WorkingCopy(T wrapped, Consumer cleanup, File directory) { + super(wrapped, cleanup); + this.directory = directory; + } + + @Override + public void close() { + super.close(); + try { + IOUtil.delete(directory); + } catch (IOException e) { + LOG.warn("could not delete temporary workdir '{}'", directory, e); + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java index f93713a221..d3ed353677 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitWorkdirFactory.java @@ -1,8 +1,8 @@ package sonia.scm.repository; +import org.eclipse.jgit.lib.Repository; import sonia.scm.repository.spi.GitContext; -import sonia.scm.repository.spi.WorkingCopy; +import sonia.scm.repository.util.WorkdirFactory; -public interface GitWorkdirFactory { - WorkingCopy createWorkingCopy(GitContext gitContext); +public interface GitWorkdirFactory extends WorkdirFactory { } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java index 23be8883fa..42667f6e51 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java @@ -35,26 +35,44 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.transport.PushResult; +import org.eclipse.jgit.transport.RemoteRefUpdate; import sonia.scm.repository.Branch; import sonia.scm.repository.GitUtil; +import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; +import sonia.scm.repository.util.WorkingCopy; -import java.io.IOException; +import java.util.stream.StreamSupport; public class GitBranchCommand extends AbstractGitCommand implements BranchCommand { - GitBranchCommand(GitContext context, Repository repository) { + private final GitWorkdirFactory workdirFactory; + + GitBranchCommand(GitContext context, Repository repository, GitWorkdirFactory workdirFactory) { super(context, repository); + this.workdirFactory = workdirFactory; } @Override - public Branch branch(String name) throws IOException { - try (Git git = new Git(open())) { - Ref ref = git.branchCreate().setName(name).call(); + public Branch branch(String name) { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { + Git clone = new Git(workingCopy.get()); + Ref ref = clone.branchCreate().setName(name).call(); + Iterable call = clone.push().add(name).call(); + StreamSupport.stream(call.spliterator(), false) + .flatMap(pushResult -> pushResult.getRemoteUpdates().stream()) + .filter(remoteRefUpdate -> remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) + .findFirst() + .ifPresent(this::handlePushError); return Branch.normalBranch(name, GitUtil.getId(ref.getObjectId())); } catch (GitAPIException ex) { throw new InternalRepositoryException(repository, "could not create branch " + name, ex); } } + + private void handlePushError(RemoteRefUpdate remoteRefUpdate) { + // TODO handle failed remote update + } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index f328011815..f402d63c34 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -20,6 +20,7 @@ import sonia.scm.repository.Person; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeDryRunCommandResult; +import sonia.scm.repository.util.WorkingCopy; import sonia.scm.user.User; import java.io.IOException; @@ -48,7 +49,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand public MergeCommandResult merge(MergeCommandRequest request) { RepositoryPermissions.push(context.getRepository().getId()).check(); - try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { Repository repository = workingCopy.get(); logger.debug("cloned repository to folder {}", repository.getWorkTree()); return new MergeWorker(repository, request).merge(); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index 898c5875d3..f4b19d1e85 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -129,7 +129,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider @Override public BranchCommand getBranchCommand() { - return new GitBranchCommand(context, repository); + return new GitBranchCommand(context, repository, handler.getWorkdirFactory()); } /** diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java index f12818aa80..bd38329ea3 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java @@ -9,59 +9,40 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.util.SimpleWorkdirFactory; +import sonia.scm.repository.util.WorkingCopy; import java.io.File; import java.io.IOException; import java.nio.file.Files; -public class SimpleGitWorkdirFactory implements GitWorkdirFactory { - - private static final Logger logger = LoggerFactory.getLogger(SimpleGitWorkdirFactory.class); - - private final File poolDirectory; +public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory { public SimpleGitWorkdirFactory() { - this(new File(System.getProperty("java.io.tmpdir"), "scmm-git-pool")); + super(null, new GitCloneProvider()); } public SimpleGitWorkdirFactory(File poolDirectory) { - this.poolDirectory = poolDirectory; - poolDirectory.mkdirs(); + super(poolDirectory, null, new GitCloneProvider()); } - public WorkingCopy createWorkingCopy(GitContext gitContext) { - try { - Repository clone = cloneRepository(gitContext.getDirectory(), createNewWorkdir()); - return new WorkingCopy(clone, this::close); - } catch (GitAPIException e) { - throw new InternalRepositoryException(gitContext.getRepository(), "could not clone working copy of repository", e); - } catch (IOException e) { - throw new InternalRepositoryException(gitContext.getRepository(), "could not create temporary directory for clone of repository", e); + private static class GitCloneProvider implements CloneProvider { + + @Override + public Repository cloneRepository(GitContext context, File target) { + try { + return Git.cloneRepository() + .setURI(createScmTransportProtocolUri(context.getDirectory())) + .setDirectory(target) + .call() + .getRepository(); + } catch (GitAPIException e) { + throw new InternalRepositoryException(context.getRepository(), "could not clone working copy of repository", e); + } } - } - private File createNewWorkdir() throws IOException { - return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); - } - - protected Repository cloneRepository(File bareRepository, File target) throws GitAPIException { - return Git.cloneRepository() - .setURI(createScmTransportProtocolUri(bareRepository)) - .setDirectory(target) - .call() - .getRepository(); - } - - private String createScmTransportProtocolUri(File bareRepository) { - return ScmTransportProtocol.NAME + "://" + bareRepository.getAbsolutePath(); - } - - private void close(Repository repository) { - repository.close(); - try { - FileUtils.delete(repository.getWorkTree(), FileUtils.RECURSIVE); - } catch (IOException e) { - logger.warn("could not delete temporary git workdir '{}'", repository.getWorkTree(), e); + private String createScmTransportProtocolUri(File bareRepository) { + return ScmTransportProtocol.NAME + "://" + bareRepository.getAbsolutePath(); } } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/WorkingCopy.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/WorkingCopy.java deleted file mode 100644 index fd0cba510b..0000000000 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/WorkingCopy.java +++ /dev/null @@ -1,12 +0,0 @@ -package sonia.scm.repository.spi; - -import org.eclipse.jgit.lib.Repository; -import sonia.scm.repository.CloseableWrapper; - -import java.util.function.Consumer; - -public class WorkingCopy extends CloseableWrapper { - WorkingCopy(Repository wrapped, Consumer cleanup) { - super(wrapped, cleanup); - } -} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java index e92ee7abb5..3bf0cb8ef7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java @@ -1,6 +1,7 @@ package sonia.scm.repository; import org.junit.Test; +import sonia.scm.repository.util.CloseableWrapper; import java.util.function.Consumer; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java index f2a4ed4954..8c4b682b18 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java @@ -34,11 +34,19 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- +import org.eclipse.jgit.transport.ScmTransportProtocol; +import org.eclipse.jgit.transport.Transport; import org.junit.After; +import org.junit.Before; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.GitRepositoryConfig; +import sonia.scm.repository.PreProcessorUtil; +import sonia.scm.repository.api.HookContextFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory; +import static com.google.inject.util.Providers.of; +import static org.mockito.Mockito.mock; + /** * * @author Sebastian Sdorra @@ -105,4 +113,5 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase /** Field description */ private GitContext context; + private ScmTransportProtocol scmTransportProtocol; } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java new file mode 100644 index 0000000000..49800fc9e8 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java @@ -0,0 +1,38 @@ +package sonia.scm.repository.spi; + +import org.eclipse.jgit.transport.ScmTransportProtocol; +import org.eclipse.jgit.transport.Transport; +import org.junit.rules.ExternalResource; +import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.PreProcessorUtil; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.repository.api.HookContextFactory; + +import static com.google.inject.util.Providers.of; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BindTransportProtocolRule extends ExternalResource { + + private ScmTransportProtocol scmTransportProtocol; + + @Override + protected void before() throws Throwable { + HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); + RepositoryManager repositoryManager = mock(RepositoryManager.class); + HookEventFacade hookEventFacade = new HookEventFacade(of(repositoryManager), hookContextFactory); + GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class); + scmTransportProtocol = new ScmTransportProtocol(of(hookEventFacade), of(gitRepositoryHandler)); + + Transport.register(scmTransportProtocol); + + when(gitRepositoryHandler.getRepositoryId(any())).thenReturn("1"); + when(repositoryManager.get("1")).thenReturn(new sonia.scm.repository.Repository()); + } + + @Override + protected void after() { + Transport.unregister(scmTransportProtocol); + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java index d5dfc17698..83f211fed8 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java @@ -1,6 +1,7 @@ package sonia.scm.repository.spi; import org.assertj.core.api.Assertions; +import org.junit.Rule; import org.junit.Test; import sonia.scm.repository.Branch; @@ -10,13 +11,16 @@ import java.util.List; public class GitBranchCommandTest extends AbstractGitCommandTestBase { + @Rule + public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule(); + @Test public void shouldCreateBranch() throws IOException { GitContext context = createContext(); Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); - new GitBranchCommand(context, repository).branch("new_branch"); + new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory()).branch("new_branch"); Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java index c926935496..e8a62f5b86 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitMergeCommandTest.java @@ -41,27 +41,8 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase { @Rule public ShiroRule shiro = new ShiroRule(); - - private ScmTransportProtocol scmTransportProtocol; - - @Before - public void bindScmProtocol() { - HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); - RepositoryManager repositoryManager = mock(RepositoryManager.class); - HookEventFacade hookEventFacade = new HookEventFacade(of(repositoryManager), hookContextFactory); - GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class); - scmTransportProtocol = new ScmTransportProtocol(of(hookEventFacade), of(gitRepositoryHandler)); - - Transport.register(scmTransportProtocol); - - when(gitRepositoryHandler.getRepositoryId(any())).thenReturn("1"); - when(repositoryManager.get("1")).thenReturn(new sonia.scm.repository.Repository()); - } - - @After - public void unregisterScmProtocol() { - Transport.unregister(scmTransportProtocol); - } + @Rule + public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule(); @Test public void shouldDetectMergeableBranches() { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkdirFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkdirFactoryTest.java index 70c34fa122..4b8e32cb09 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkdirFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkdirFactoryTest.java @@ -12,6 +12,7 @@ import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.api.HookContextFactory; +import sonia.scm.repository.util.WorkingCopy; import java.io.File; import java.io.IOException; @@ -44,7 +45,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder()); File masterRepo = createRepositoryDirectory(); - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { assertThat(workingCopy.get().getDirectory()) .exists() @@ -57,25 +58,15 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { } } - @Test - public void cloneFromPoolShouldBeClosed() throws IOException { - PoolWithSpy factory = new PoolWithSpy(temporaryFolder.newFolder()); - - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { - assertThat(workingCopy).isNotNull(); - } - verify(factory.createdClone).close(); - } - @Test public void cloneFromPoolShouldNotBeReused() throws IOException { SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder()); File firstDirectory; - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { firstDirectory = workingCopy.get().getDirectory(); } - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { File secondDirectory = workingCopy.get().getDirectory(); assertThat(secondDirectory).isNotEqualTo(firstDirectory); } @@ -86,23 +77,9 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder()); File directory; - try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { + try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { directory = workingCopy.get().getWorkTree(); } assertThat(directory).doesNotExist(); } - - private static class PoolWithSpy extends SimpleGitWorkdirFactory { - PoolWithSpy(File poolDirectory) { - super(poolDirectory); - } - - Repository createdClone; - - @Override - protected Repository cloneRepository(File bareRepository, File destination) throws GitAPIException { - createdClone = spy(super.cloneRepository(bareRepository, destination)); - return createdClone; - } - } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java index d2da936c48..4ccf13e738 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java @@ -53,6 +53,7 @@ import sonia.scm.io.INISection; import sonia.scm.plugin.Extension; import sonia.scm.plugin.PluginLoader; import sonia.scm.repository.spi.HgRepositoryServiceProvider; +import sonia.scm.repository.spi.HgWorkdirFactory; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.util.IOUtil; import sonia.scm.util.SystemUtil; @@ -113,10 +114,11 @@ public class HgRepositoryHandler public HgRepositoryHandler(ConfigurationStoreFactory storeFactory, Provider hgContextProvider, RepositoryLocationResolver repositoryLocationResolver, - PluginLoader pluginLoader) + PluginLoader pluginLoader, HgWorkdirFactory workdirFactory) { super(storeFactory, repositoryLocationResolver, pluginLoader); this.hgContextProvider = hgContextProvider; + this.workdirFactory = workdirFactory; try { @@ -408,6 +410,10 @@ public class HgRepositoryHandler } } + public HgWorkdirFactory getWorkdirFactory() { + return workdirFactory; + } + //~--- fields --------------------------------------------------------------- /** Field description */ @@ -415,4 +421,6 @@ public class HgRepositoryHandler /** Field description */ private JAXBContext jaxbContext; + + private final HgWorkdirFactory workdirFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java index 01c5c5fc98..99976f59df 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java @@ -32,10 +32,14 @@ package sonia.scm.repository.spi; import com.aragost.javahg.Changeset; import com.aragost.javahg.commands.CommitCommand; +import com.aragost.javahg.commands.PushCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.Repository; +import sonia.scm.repository.util.WorkingCopy; + +import java.io.IOException; /** * Mercurial implementation of the {@link BranchCommand}. @@ -45,24 +49,37 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { private static final Logger LOG = LoggerFactory.getLogger(HgBranchCommand.class); - HgBranchCommand(HgCommandContext context, Repository repository) { + private final HgWorkdirFactory workdirFactory; + + HgBranchCommand(HgCommandContext context, Repository repository, HgWorkdirFactory workdirFactory) { super(context, repository); + this.workdirFactory = workdirFactory; } @Override - public Branch branch(String name) { - com.aragost.javahg.Repository repository = open(); - com.aragost.javahg.commands.BranchCommand.on(repository).set(name); + public Branch branch(String name) throws IOException { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext())) { + com.aragost.javahg.Repository repository = workingCopy.get().get(); + com.aragost.javahg.commands.BranchCommand.on(repository).set(name); - Changeset emptyChangeset = CommitCommand - .on(repository) - .user("SCM-Manager") - .message("Create new branch " + name) - .execute(); + Changeset emptyChangeset = CommitCommand + .on(repository) + .user("SCM-Manager") + .message("Create new branch " + name) + .execute(); - LOG.debug("Created new branch '{}' in repository {} with changeset {}", - name, getRepository().getNamespaceAndName(), emptyChangeset.getNode()); + PushCommand pushCommand = PushCommand + .on(repository) + .branch(name) + .newBranch(); + pushCommand + .cmdAppend("--config", ""); + pushCommand .execute(); - return Branch.normalBranch(name, emptyChangeset.getNode()); + LOG.debug("Created new branch '{}' in repository {} with changeset {}", + name, getRepository().getNamespaceAndName(), emptyChangeset.getNode()); + + return Branch.normalBranch(name, emptyChangeset.getNode()); + } } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java index 0b88c07cb1..d60e888cac 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java @@ -128,7 +128,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider @Override public BranchCommand getBranchCommand() { - return new HgBranchCommand(context, repository); + return new HgBranchCommand(context, repository, handler.getWorkdirFactory()); } /** diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java new file mode 100644 index 0000000000..b32b485cca --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java @@ -0,0 +1,6 @@ +package sonia.scm.repository.spi; + +import sonia.scm.repository.util.WorkdirFactory; + +public interface HgWorkdirFactory extends WorkdirFactory { +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java new file mode 100644 index 0000000000..b893b53fec --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java @@ -0,0 +1,43 @@ +package sonia.scm.repository.spi; + +import com.aragost.javahg.Repository; +import com.aragost.javahg.commands.CloneCommand; +import sonia.scm.repository.util.SimpleWorkdirFactory; + +import java.io.File; +import java.io.IOException; + +public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory implements HgWorkdirFactory { + public SimpleHgWorkdirFactory() { + super(null, new HgCloneProvider()); + } + + public SimpleHgWorkdirFactory(File poolDirectory) { + super(poolDirectory, null, new HgCloneProvider()); + } + + private static class HgCloneProvider implements CloneProvider { + + @Override + public RepositoryCloseableWrapper cloneRepository(HgCommandContext context, File target) throws IOException { + String execute = CloneCommand.on(context.open()).execute(target.getAbsolutePath()); + return new RepositoryCloseableWrapper(Repository.open(target)); + } + } +} + +class RepositoryCloseableWrapper implements AutoCloseable { + private final Repository delegate; + + RepositoryCloseableWrapper(Repository delegate) { + this.delegate = delegate; + } + + Repository get() { + return delegate; + } + + @Override + public void close() { + } +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java index ba9ae3a0b9..7b66688036 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgServletModule.java @@ -46,6 +46,8 @@ import sonia.scm.plugin.Extension; import sonia.scm.repository.HgContext; import sonia.scm.repository.HgContextProvider; import sonia.scm.repository.HgHookManager; +import sonia.scm.repository.spi.HgWorkdirFactory; +import sonia.scm.repository.spi.SimpleHgWorkdirFactory; /** * @@ -81,5 +83,7 @@ public class HgServletModule extends ServletModule // bind servlets serve(MAPPING_HOOK).with(HgHookCallbackServlet.class); + + bind(HgWorkdirFactory.class).to(SimpleHgWorkdirFactory.class); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java index ed222f5119..c3b66525f9 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java @@ -77,7 +77,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) { - HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null); + HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null, null); handler.init(contextProvider); HgTestUtil.checkForSkip(handler); @@ -87,7 +87,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Test public void getDirectory() { - HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null); + HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null, null); HgConfig hgConfig = new HgConfig(); hgConfig.setHgBinary("hg"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java index 131dad0837..1cecbb21be 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java @@ -105,7 +105,7 @@ public final class HgTestUtil RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver()); HgRepositoryHandler handler = - new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null); + new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null); Path repoDir = directory.toPath(); when(repoDao.getPath(any())).thenReturn(repoDir); handler.init(context); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java index 8c0b1862d1..24b262948f 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -4,14 +4,15 @@ import org.assertj.core.api.Assertions; import org.junit.Test; import sonia.scm.repository.Branch; +import java.io.IOException; import java.util.List; public class HgBranchCommandTest extends AbstractHgCommandTestBase { @Test - public void x() { + public void x() throws IOException { Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); - new HgBranchCommand(cmdContext, repository).branch("new_branch"); + new HgBranchCommand(cmdContext, repository, new SimpleHgWorkdirFactory()).branch("new_branch"); Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); } From 2f4f1dbe0200e18153ba5196abfb42c8c6a19859 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Wed, 27 Mar 2019 10:12:05 +0100 Subject: [PATCH 11/31] add prop to the autocomplete component to indicate if an automatic create action is required --- .../ui-components/src/Autocomplete.js | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/Autocomplete.js b/scm-ui-components/packages/ui-components/src/Autocomplete.js index f3023e268b..adf86e37b7 100644 --- a/scm-ui-components/packages/ui-components/src/Autocomplete.js +++ b/scm-ui-components/packages/ui-components/src/Autocomplete.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import { AsyncCreatable } from "react-select"; +import { AsyncCreatable, Async } from "react-select"; import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types"; import LabelWithHelpIcon from "./forms/LabelWithHelpIcon"; @@ -13,7 +13,8 @@ type Props = { value?: SelectValue, placeholder: string, loadingMessage: string, - noOptionsMessage: string + noOptionsMessage: string, + creatable?: boolean }; @@ -42,27 +43,40 @@ class Autocomplete extends React.Component { }; render() { - const { label, helpText, value, placeholder, loadingMessage, noOptionsMessage, loadSuggestions } = this.props; + const { label, helpText, value, placeholder, loadingMessage, noOptionsMessage, loadSuggestions, creatable } = this.props; return (
- loadingMessage} - noOptionsMessage={() => noOptionsMessage} - isValidNewOption={this.isValidNewOption} - onCreateOption={value => { - this.handleInputChange({ - label: value, - value: { id: value, displayName: value } - }); - }} - /> + {creatable? + loadingMessage} + noOptionsMessage={() => noOptionsMessage} + isValidNewOption={this.isValidNewOption} + onCreateOption={value => { + this.handleInputChange({ + label: value, + value: { id: value, displayName: value } + }); + }} + /> + : + loadingMessage} + noOptionsMessage={() => noOptionsMessage} + /> + + }
); From daaa50f08c5904b7b84df1834e40ef5d1bd64934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 27 Mar 2019 10:57:09 +0100 Subject: [PATCH 12/31] Move environment preparations to new class --- .../main/java/sonia/scm/web/HgCGIServlet.java | 113 ++---------------- .../web/HgRepositoryEnvironmentBuilder.java | 96 +++++++++++++++ 2 files changed, 104 insertions(+), 105 deletions(-) create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java index 45eba94442..5084e4d38b 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java @@ -34,7 +34,6 @@ package sonia.scm.web; import com.google.common.base.Stopwatch; -import com.google.common.base.Strings; import com.google.inject.Inject; import com.google.inject.Singleton; import org.slf4j.Logger; @@ -42,16 +41,12 @@ import org.slf4j.LoggerFactory; import sonia.scm.SCMContext; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.HgConfig; -import sonia.scm.repository.HgEnvironment; -import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgPythonScript; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryRequestListenerUtil; import sonia.scm.repository.spi.ScmProviderHttpServlet; -import sonia.scm.security.CipherUtil; import sonia.scm.util.AssertUtil; -import sonia.scm.util.HttpUtil; import sonia.scm.web.cgi.CGIExecutor; import sonia.scm.web.cgi.CGIExecutorFactory; import sonia.scm.web.cgi.EnvList; @@ -62,14 +57,12 @@ import java.io.File; import java.io.IOException; import java.util.Enumeration; -import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import java.util.Base64; /** * @@ -79,25 +72,9 @@ import java.util.Base64; public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet { - private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY"; - - /** Field description */ - public static final String ENV_REPOSITORY_NAME = "REPO_NAME"; - - /** Field description */ - public static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH"; - - /** Field description */ - public static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID"; - - private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS"; - /** Field description */ public static final String ENV_SESSION_PREFIX = "SCM_"; - /** Field description */ - private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; - /** Field description */ private static final long serialVersionUID = -3492811300905099810L; @@ -107,30 +84,18 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet //~--- constructors --------------------------------------------------------- - /** - * Constructs ... - * - * - * - * - * - * @param cgiExecutorFactory - * @param configuration - * @param handler - * @param hookManager - * @param requestListenerUtil - */ @Inject public HgCGIServlet(CGIExecutorFactory cgiExecutorFactory, - ScmConfiguration configuration, - HgRepositoryHandler handler, HgHookManager hookManager, - RepositoryRequestListenerUtil requestListenerUtil) + ScmConfiguration configuration, + HgRepositoryHandler handler, + RepositoryRequestListenerUtil requestListenerUtil, + HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder) { this.cgiExecutorFactory = cgiExecutorFactory; this.configuration = configuration; this.handler = handler; - this.hookManager = hookManager; this.requestListenerUtil = requestListenerUtil; + this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder; this.exceptionHandler = new HgCGIExceptionHandler(); this.command = HgPythonScript.HGWEB.getFile(SCMContext.getContext()); } @@ -163,40 +128,6 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet } } - /** - * Method description - * - * - * @param env - * @param request - */ - private void addCredentials(EnvList env, HttpServletRequest request) - { - String authorization = request.getHeader(HttpUtil.HEADER_AUTHORIZATION); - - if (!Strings.isNullOrEmpty(authorization)) - { - if (authorization.startsWith(HttpUtil.AUTHORIZATION_SCHEME_BASIC)) - { - String encodedUserInfo = - authorization.substring( - HttpUtil.AUTHORIZATION_SCHEME_BASIC.length()).trim(); - // TODO check encoding of user-agent ? - String userInfo = new String(Base64.getDecoder().decode(encodedUserInfo)); - - env.set(SCM_CREDENTIALS, CipherUtil.getInstance().encode(userInfo)); - } - else - { - logger.warn("unknow authentication scheme used"); - } - } - else - { - logger.trace("no authorization header found"); - } - } - /** * Method description * @@ -262,7 +193,6 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet HttpServletResponse response, Repository repository) throws IOException, ServletException { - File directory = handler.getDirectory(repository.getId()); CGIExecutor executor = cgiExecutorFactory.createExecutor(configuration, getServletContext(), request, response); @@ -271,33 +201,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet executor.setExceptionHandler(exceptionHandler); executor.setStatusCodeHandler(exceptionHandler); executor.setContentLengthWorkaround(true); - executor.getEnvironment().set(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName()); - executor.getEnvironment().set(ENV_REPOSITORY_ID, repository.getId()); - executor.getEnvironment().set(ENV_REPOSITORY_PATH, - directory.getAbsolutePath()); - - // add hook environment - Map environment = executor.getEnvironment().asMutableMap(); - if (handler.getConfig().isDisableHookSSLValidation()) { - // disable ssl validation - // Issue 959: https://goo.gl/zH5eY8 - environment.put(ENV_PYTHON_HTTPS_VERIFY, "0"); - } - - // enable experimental httppostargs protocol of mercurial - // Issue 970: https://goo.gl/poascp - environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs())); - - //J- - HgEnvironment.prepareEnvironment( - environment, - handler, - hookManager, - request - ); - //J+ - - addCredentials(executor.getEnvironment(), request); + hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment()); // unused ??? HttpSession session = request.getSession(false); @@ -358,9 +262,8 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet /** Field description */ private final HgRepositoryHandler handler; - /** Field description */ - private final HgHookManager hookManager; - /** Field description */ private final RepositoryRequestListenerUtil requestListenerUtil; + + private final HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java new file mode 100644 index 0000000000..3e7e590270 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java @@ -0,0 +1,96 @@ +package sonia.scm.web; + +import com.google.common.base.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.HgEnvironment; +import sonia.scm.repository.HgHookManager; +import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.repository.Repository; +import sonia.scm.security.CipherUtil; +import sonia.scm.util.HttpUtil; +import sonia.scm.web.cgi.EnvList; + +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.Base64; +import java.util.Map; + +public class HgRepositoryEnvironmentBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(HgRepositoryEnvironmentBuilder.class); + + private static final String ENV_REPOSITORY_NAME = "REPO_NAME"; + private static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH"; + private static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID"; + private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY"; + private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS"; + private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; + + private final HgRepositoryHandler handler; + private final HgHookManager hookManager; + + @Inject + public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager) { + this.handler = handler; + this.hookManager = hookManager; + } + + void buildFor(Repository repository, HttpServletRequest request, EnvList environment) { + File directory = handler.getDirectory(repository.getId()); + + environment.set(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName()); + environment.set(ENV_REPOSITORY_ID, repository.getId()); + environment.set(ENV_REPOSITORY_PATH, + directory.getAbsolutePath()); + + // add hook environment + Map environmentMap = environment.asMutableMap(); + if (handler.getConfig().isDisableHookSSLValidation()) { + // disable ssl validation + // Issue 959: https://goo.gl/zH5eY8 + environmentMap.put(ENV_PYTHON_HTTPS_VERIFY, "0"); + } + + // enable experimental httppostargs protocol of mercurial + // Issue 970: https://goo.gl/poascp + environmentMap.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs())); + + HgEnvironment.prepareEnvironment( + environmentMap, + handler, + hookManager, + request + ); + + addCredentials(environment, request); + } + + private void addCredentials(EnvList env, HttpServletRequest request) + { + String authorization = request.getHeader(HttpUtil.HEADER_AUTHORIZATION); + + if (!Strings.isNullOrEmpty(authorization)) + { + if (authorization.startsWith(HttpUtil.AUTHORIZATION_SCHEME_BASIC)) + { + String encodedUserInfo = + authorization.substring( + HttpUtil.AUTHORIZATION_SCHEME_BASIC.length()).trim(); + // TODO check encoding of user-agent ? + String userInfo = new String(Base64.getDecoder().decode(encodedUserInfo)); + + env.set(SCM_CREDENTIALS, CipherUtil.getInstance().encode(userInfo)); + } + else + { + LOG.warn("unknown authentication scheme used"); + } + } + else + { + LOG.trace("no authorization header found"); + } + } +} From cc4bd6ddd13cf8b55f5823ae065be64f114c98a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 28 Mar 2019 08:40:56 +0100 Subject: [PATCH 13/31] Use bearer tokens to authenticate hg hook callbacks --- .../repository/util/SimpleWorkdirFactory.java | 14 ++--- .../spi/SimpleGitWorkdirFactory.java | 15 +++-- scm-plugins/scm-hg-plugin/pom.xml | 5 ++ .../sonia/scm/repository/HgEnvironment.java | 54 +++------------- .../scm/repository/spi/HgBranchCommand.java | 13 +--- .../scm/repository/spi/HgCommandContext.java | 47 +++++++++----- .../spi/HgHookChangesetProvider.java | 10 ++- .../repository/spi/HgHookContextProvider.java | 5 +- .../spi/HgRepositoryServiceProvider.java | 5 +- .../spi/HgRepositoryServiceResolver.java | 11 ++-- .../spi/SimpleHgWorkdirFactory.java | 60 +++++++++++++++--- .../main/java/sonia/scm/web/HgCGIServlet.java | 2 +- .../sonia/scm/web/HgHookCallbackServlet.java | 52 +++++++-------- .../web/HgRepositoryEnvironmentBuilder.java | 63 ++++++------------- .../src/main/java/sonia/scm/web/HgUtil.java | 22 ++++++- .../resources/sonia/scm/python/scmhooks.py | 6 +- .../spi/AbstractHgCommandTestBase.java | 2 +- .../repository/spi/HgBranchCommandTest.java | 29 ++++++++- .../repository/spi/HgIncomingCommandTest.java | 2 +- .../spi/HgModificationsCommandTest.java | 2 +- .../repository/spi/HgOutgoingCommandTest.java | 2 +- .../scm/web/HgHookCallbackServletTest.java | 2 +- 22 files changed, 232 insertions(+), 191 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java index f2d436dffd..3b761077a2 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -9,23 +9,21 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -public class SimpleWorkdirFactory { +public abstract class SimpleWorkdirFactory { private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class); private final File poolDirectory; private final CloneProvider cloneProvider; - private final Repository repository; - public SimpleWorkdirFactory(Repository repository, CloneProvider cloneProvider) { - this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool"), repository, cloneProvider); + public SimpleWorkdirFactory(CloneProvider cloneProvider) { + this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool"), cloneProvider); } - public SimpleWorkdirFactory(File poolDirectory, Repository repository, CloneProvider cloneProvider) { + public SimpleWorkdirFactory(File poolDirectory, CloneProvider cloneProvider) { this.poolDirectory = poolDirectory; this.cloneProvider = cloneProvider; - this.repository = repository; poolDirectory.mkdirs(); } @@ -35,10 +33,12 @@ public class SimpleWorkdirFactory { T clone = cloneProvider.cloneRepository(context, directory); return new WorkingCopy<>(clone, this::close, directory); } catch (IOException e) { - throw new InternalRepositoryException(repository, "could not create temporary directory for clone of repository", e); + throw new InternalRepositoryException(getRepository(context), "could not create temporary directory for clone of repository", e); } } + protected abstract Repository getRepository(C context); + private File createNewWorkdir() throws IOException { return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java index bd38329ea3..d9fed92968 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java @@ -4,26 +4,20 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.ScmTransportProtocol; -import org.eclipse.jgit.util.FileUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.util.SimpleWorkdirFactory; -import sonia.scm.repository.util.WorkingCopy; import java.io.File; -import java.io.IOException; -import java.nio.file.Files; public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory { public SimpleGitWorkdirFactory() { - super(null, new GitCloneProvider()); + super(new GitCloneProvider()); } public SimpleGitWorkdirFactory(File poolDirectory) { - super(poolDirectory, null, new GitCloneProvider()); + super(poolDirectory, new GitCloneProvider()); } private static class GitCloneProvider implements CloneProvider { @@ -45,4 +39,9 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory + + io.jsonwebtoken + jjwt + 0.4 + diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java index d80c0c5afb..c87cc0acea 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java @@ -35,15 +35,9 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.base.Strings; - -import org.apache.shiro.codec.Base64; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import sonia.scm.security.AccessToken; +import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.security.CipherUtil; -import sonia.scm.util.HttpUtil; import sonia.scm.web.HgUtil; //~--- JDK imports ------------------------------------------------------------ @@ -68,14 +62,7 @@ public final class HgEnvironment /** Field description */ private static final String ENV_URL = "SCM_URL"; - /** Field description */ - private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; - - /** - * the logger for HgEnvironment - */ - private static final Logger logger = - LoggerFactory.getLogger(HgEnvironment.class); + private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN"; //~--- constructors --------------------------------------------------------- @@ -98,14 +85,14 @@ public final class HgEnvironment */ public static void prepareEnvironment(Map environment, HgRepositoryHandler handler, HgHookManager hookManager, - HttpServletRequest request) + HttpServletRequest request, AccessTokenBuilderFactory accessTokenBuilderFactory) { String hookUrl; if (request != null) { hookUrl = hookManager.createUrl(request); - environment.put(SCM_CREDENTIALS, getCredentials(request)); + environment.put(SCM_BEARER_TOKEN, getCredentials(accessTokenBuilderFactory)); } else { @@ -126,9 +113,9 @@ public final class HgEnvironment * @param hookManager */ public static void prepareEnvironment(Map environment, - HgRepositoryHandler handler, HgHookManager hookManager) + HgRepositoryHandler handler, HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory) { - prepareEnvironment(environment, handler, hookManager, null); + prepareEnvironment(environment, handler, hookManager, null, accessTokenBuilderFactory); } //~--- get methods ---------------------------------------------------------- @@ -139,31 +126,10 @@ public final class HgEnvironment * * @return */ - private static String getCredentials(HttpServletRequest request) + private static String getCredentials(AccessTokenBuilderFactory accessTokenBuilderFactory) { - String credentials = null; - String header = request.getHeader(HttpUtil.HEADER_AUTHORIZATION); + AccessToken accessToken = accessTokenBuilderFactory.create().build(); - if (!Strings.isNullOrEmpty(header)) - { - String[] parts = header.split("\\s+"); - - if (parts.length > 0) - { - CipherUtil cu = CipherUtil.getInstance(); - - credentials = cu.encode(Base64.decodeToString(parts[1])); - } - else - { - logger.warn("invalid basic authentication header"); - } - } - else - { - logger.warn("could not find authentication header on request"); - } - - return Strings.nullToEmpty(credentials); + return CipherUtil.getInstance().encode(accessToken.compact()); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java index 99976f59df..0d69e5bc75 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java @@ -32,15 +32,12 @@ package sonia.scm.repository.spi; import com.aragost.javahg.Changeset; import com.aragost.javahg.commands.CommitCommand; -import com.aragost.javahg.commands.PushCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.Repository; import sonia.scm.repository.util.WorkingCopy; -import java.io.IOException; - /** * Mercurial implementation of the {@link BranchCommand}. * Note that this creates an empty commit to "persist" the new branch. @@ -57,7 +54,7 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { } @Override - public Branch branch(String name) throws IOException { + public Branch branch(String name) { try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext())) { com.aragost.javahg.Repository repository = workingCopy.get().get(); com.aragost.javahg.commands.BranchCommand.on(repository).set(name); @@ -68,14 +65,6 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { .message("Create new branch " + name) .execute(); - PushCommand pushCommand = PushCommand - .on(repository) - .branch(name) - .newBranch(); - pushCommand - .cmdAppend("--config", ""); - pushCommand .execute(); - LOG.debug("Created new branch '{}' in repository {} with changeset {}", name, getRepository().getNamespaceAndName(), emptyChangeset.getNode()); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java index fee73513cd..e21a8adca0 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java @@ -42,6 +42,7 @@ import com.google.common.base.Strings; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.web.HgUtil; //~--- JDK imports ------------------------------------------------------------ @@ -49,6 +50,8 @@ import sonia.scm.web.HgUtil; import java.io.Closeable; import java.io.File; import java.io.IOException; +import java.util.Map; +import java.util.function.BiConsumer; /** * @@ -66,44 +69,46 @@ public class HgCommandContext implements Closeable * Constructs ... * * - * - * @param hookManager + * @param hookManager * @param handler * @param repository * @param directory + * @param accessTokenBuilderFactory */ public HgCommandContext(HgHookManager hookManager, - HgRepositoryHandler handler, sonia.scm.repository.Repository repository, - File directory) + HgRepositoryHandler handler, sonia.scm.repository.Repository repository, + File directory, AccessTokenBuilderFactory accessTokenBuilderFactory) { this(hookManager, handler, repository, directory, - handler.getHgContext().isPending()); + handler.getHgContext().isPending(), accessTokenBuilderFactory); } /** * Constructs ... * * - * - * @param hookManager - * @param hanlder + * @param hookManager + * @param handler * @param repository * @param directory * @param pending + * @param accessTokenBuilderFactory */ public HgCommandContext(HgHookManager hookManager, - HgRepositoryHandler hanlder, sonia.scm.repository.Repository repository, - File directory, boolean pending) + HgRepositoryHandler handler, sonia.scm.repository.Repository repository, + File directory, boolean pending, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.hookManager = hookManager; - this.hanlder = hanlder; + this.handler = handler; this.directory = directory; + this.scmRepository = repository; this.encoding = repository.getProperty(PROPERTY_ENCODING); this.pending = pending; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; if (Strings.isNullOrEmpty(encoding)) { - encoding = hanlder.getConfig().getEncoding(); + encoding = handler.getConfig().getEncoding(); } } @@ -134,13 +139,19 @@ public class HgCommandContext implements Closeable { if (repository == null) { - repository = HgUtil.open(hanlder, hookManager, directory, encoding, - pending); + repository = HgUtil.open(handler, hookManager, directory, encoding, + pending, accessTokenBuilderFactory); } return repository; } + public Repository openWithSpecialEnvironment(BiConsumer> prepareEnvironment) + { + return HgUtil.open(handler, directory, encoding, + pending, environment -> prepareEnvironment.accept(scmRepository, environment)); + } + //~--- get methods ---------------------------------------------------------- /** @@ -151,7 +162,7 @@ public class HgCommandContext implements Closeable */ public HgConfig getConfig() { - return hanlder.getConfig(); + return handler.getConfig(); } //~--- fields --------------------------------------------------------------- @@ -163,7 +174,7 @@ public class HgCommandContext implements Closeable private String encoding; /** Field description */ - private HgRepositoryHandler hanlder; + private HgRepositoryHandler handler; /** Field description */ private HgHookManager hookManager; @@ -173,4 +184,8 @@ public class HgCommandContext implements Closeable /** Field description */ private Repository repository; + + private final sonia.scm.repository.Repository scmRepository; + + private final AccessTokenBuilderFactory accessTokenBuilderFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java index e4e3dc238e..21d31d9926 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java @@ -40,6 +40,7 @@ import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.spi.javahg.HgLogChangesetCommand; +import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.web.HgUtil; import java.io.File; @@ -62,14 +63,15 @@ public class HgHookChangesetProvider implements HookChangesetProvider //~--- constructors --------------------------------------------------------- public HgHookChangesetProvider(HgRepositoryHandler handler, - File repositoryDirectory, HgHookManager hookManager, String startRev, - RepositoryHookType type) + File repositoryDirectory, HgHookManager hookManager, String startRev, + RepositoryHookType type, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.handler = handler; this.repositoryDirectory = repositoryDirectory; this.hookManager = hookManager; this.startRev = startRev; this.type = type; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; } //~--- methods -------------------------------------------------------------- @@ -128,7 +130,7 @@ public class HgHookChangesetProvider implements HookChangesetProvider // TODO get repository encoding return HgUtil.open(handler, hookManager, repositoryDirectory, null, - pending); + pending, accessTokenBuilderFactory); } //~--- fields --------------------------------------------------------------- @@ -150,4 +152,6 @@ public class HgHookChangesetProvider implements HookChangesetProvider /** Field description */ private RepositoryHookType type; + + private final AccessTokenBuilderFactory accessTokenBuilderFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java index 414cfe27b8..df1ee2d501 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java @@ -43,6 +43,7 @@ import sonia.scm.repository.api.HookBranchProvider; import sonia.scm.repository.api.HookFeature; import sonia.scm.repository.api.HookMessageProvider; import sonia.scm.repository.api.HookTagProvider; +import sonia.scm.security.AccessTokenBuilderFactory; import java.io.File; import java.util.EnumSet; @@ -75,9 +76,9 @@ public class HgHookContextProvider extends HookContextProvider */ public HgHookContextProvider(HgRepositoryHandler handler, File repositoryDirectory, HgHookManager hookManager, String startRev, - RepositoryHookType type) + RepositoryHookType type, AccessTokenBuilderFactory accessTokenBuilderFactory) { - this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type); + this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type, accessTokenBuilderFactory); } //~--- get methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java index d60e888cac..9ae71691aa 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java @@ -40,6 +40,7 @@ import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.repository.api.Command; import sonia.scm.repository.api.CommandNotSupportedException; +import sonia.scm.security.AccessTokenBuilderFactory; import java.io.File; import java.io.IOException; @@ -77,13 +78,13 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider //~--- constructors --------------------------------------------------------- HgRepositoryServiceProvider(HgRepositoryHandler handler, - HgHookManager hookManager, Repository repository) + HgHookManager hookManager, Repository repository, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.repository = repository; this.handler = handler; this.repositoryDirectory = handler.getDirectory(repository.getId()); this.context = new HgCommandContext(hookManager, handler, repository, - repositoryDirectory); + repositoryDirectory, accessTokenBuilderFactory); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java index d6d04ee017..56ce57ffcc 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java @@ -38,6 +38,7 @@ import sonia.scm.plugin.Extension; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; +import sonia.scm.security.AccessTokenBuilderFactory; /** * @@ -47,15 +48,17 @@ import sonia.scm.repository.Repository; public class HgRepositoryServiceResolver implements RepositoryServiceResolver { - private HgRepositoryHandler handler; - private HgHookManager hookManager; + private final HgRepositoryHandler handler; + private final HgHookManager hookManager; + private final AccessTokenBuilderFactory accessTokenBuilderFactory; @Inject public HgRepositoryServiceResolver(HgRepositoryHandler handler, - HgHookManager hookManager) + HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.handler = handler; this.hookManager = hookManager; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; } @Override @@ -63,7 +66,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver HgRepositoryServiceProvider provider = null; if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) { - provider = new HgRepositoryServiceProvider(handler, hookManager, repository); + provider = new HgRepositoryServiceProvider(handler, hookManager, repository, accessTokenBuilderFactory); } return provider; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java index b893b53fec..2abf4253ff 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java @@ -2,35 +2,65 @@ package sonia.scm.repository.spi; import com.aragost.javahg.Repository; import com.aragost.javahg.commands.CloneCommand; +import com.aragost.javahg.commands.PullCommand; import sonia.scm.repository.util.SimpleWorkdirFactory; +import sonia.scm.web.HgRepositoryEnvironmentBuilder; +import javax.inject.Inject; +import javax.inject.Provider; import java.io.File; import java.io.IOException; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory implements HgWorkdirFactory { - public SimpleHgWorkdirFactory() { - super(null, new HgCloneProvider()); + + @Inject + public SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder) { + this(hgRepositoryEnvironmentBuilder, new HookConfigurer()); } - public SimpleHgWorkdirFactory(File poolDirectory) { - super(poolDirectory, null, new HgCloneProvider()); + SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder, Consumer hookConfigurer) { + super(new HgCloneProvider(hgRepositoryEnvironmentBuilder, hookConfigurer)); } private static class HgCloneProvider implements CloneProvider { + private final Provider hgRepositoryEnvironmentBuilder; + private final Consumer hookConfigurer; + + private HgCloneProvider(Provider hgRepositoryEnvironmentBuilder, Consumer hookConfigurer) { + this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder; + this.hookConfigurer = hookConfigurer; + } + @Override public RepositoryCloseableWrapper cloneRepository(HgCommandContext context, File target) throws IOException { - String execute = CloneCommand.on(context.open()).execute(target.getAbsolutePath()); - return new RepositoryCloseableWrapper(Repository.open(target)); + BiConsumer> repositoryMapBiConsumer = (repository, environment) -> { + hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment); + }; + Repository centralRepository = context.openWithSpecialEnvironment(repositoryMapBiConsumer); + CloneCommand.on(centralRepository).execute(target.getAbsolutePath()); + return new RepositoryCloseableWrapper(Repository.open(target), centralRepository, hookConfigurer); } } + + @Override + protected sonia.scm.repository.Repository getRepository(HgCommandContext context) { + return null; + } } class RepositoryCloseableWrapper implements AutoCloseable { private final Repository delegate; + private final Repository centralRepository; + private final Consumer hookConfigurer; - RepositoryCloseableWrapper(Repository delegate) { + RepositoryCloseableWrapper(Repository delegate, Repository centralRepository, Consumer hookConfigurer) { this.delegate = delegate; + this.centralRepository = centralRepository; + this.hookConfigurer = hookConfigurer; } Repository get() { @@ -39,5 +69,21 @@ class RepositoryCloseableWrapper implements AutoCloseable { @Override public void close() { + try { + PullCommand pullCommand = PullCommand.on(centralRepository); + hookConfigurer.accept(pullCommand); + pullCommand.execute(delegate.getDirectory().getAbsolutePath()); + } catch (Exception e) { + throw new RuntimeException(e); + } + centralRepository.close(); + } +} + +class HookConfigurer implements Consumer { + @Override + public void accept(PullCommand pullCommand) { + pullCommand.cmdAppend("--config", "hooks.changegroup.scm=python:scmhooks.postHook"); + pullCommand.cmdAppend("--config", "hooks.pretxnchangegroup.scm=python:scmhooks.preHook"); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java index 5084e4d38b..803db0f129 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java @@ -201,7 +201,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet executor.setExceptionHandler(exceptionHandler); executor.setStatusCodeHandler(exceptionHandler); executor.setContentLengthWorkaround(true); - hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment()); + hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap()); // unused ??? HttpSession session = request.getSession(false); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java index 1b31eb11ca..3ab2a06eb3 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java @@ -42,6 +42,7 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,8 +55,9 @@ import sonia.scm.repository.api.HgHookMessage; import sonia.scm.repository.api.HgHookMessage.Severity; import sonia.scm.repository.spi.HgHookContextProvider; import sonia.scm.repository.spi.HookEventFacade; +import sonia.scm.security.AccessTokenBuilderFactory; +import sonia.scm.security.BearerToken; import sonia.scm.security.CipherUtil; -import sonia.scm.security.Tokens; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; @@ -93,7 +95,7 @@ public class HgHookCallbackServlet extends HttpServlet private static final String PARAM_CHALLENGE = "challenge"; /** Field description */ - private static final String PARAM_CREDENTIALS = "credentials"; + private static final String PARAM_TOKEN = "token"; /** Field description */ private static final String PARAM_NODE = "node"; @@ -117,12 +119,13 @@ public class HgHookCallbackServlet extends HttpServlet @Inject public HgHookCallbackServlet(HookEventFacade hookEventFacade, HgRepositoryHandler handler, HgHookManager hookManager, - Provider contextProvider) + Provider contextProvider, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.hookEventFacade = hookEventFacade; this.handler = handler; this.hookManager = hookManager; this.contextProvider = contextProvider; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; } //~--- methods -------------------------------------------------------------- @@ -179,11 +182,11 @@ public class HgHookCallbackServlet extends HttpServlet if (Util.isNotEmpty(node)) { - String credentials = request.getParameter(PARAM_CREDENTIALS); + String token = request.getParameter(PARAM_TOKEN); - if (Util.isNotEmpty(credentials)) + if (Util.isNotEmpty(token)) { - authenticate(request, credentials); + authenticate(token); } hookCallback(response, type, repositoryId, challenge, node); @@ -209,34 +212,20 @@ public class HgHookCallbackServlet extends HttpServlet } } - private void authenticate(HttpServletRequest request, String credentials) + private void authenticate(String token) { try { - credentials = CipherUtil.getInstance().decode(credentials); + token = CipherUtil.getInstance().decode(token); - if (Util.isNotEmpty(credentials)) + if (Util.isNotEmpty(token)) { - int index = credentials.indexOf(':'); + Subject subject = SecurityUtils.getSubject(); - if (index > 0 && index < credentials.length()) - { - Subject subject = SecurityUtils.getSubject(); + AuthenticationToken accessToken = createToken(token); - //J- - subject.login( - Tokens.createAuthenticationToken( - request, - credentials.substring(0, index), - credentials.substring(index + 1) - ) - ); - //J+ - } - else - { - logger.error("could not find delimiter"); - } + //J- + subject.login(accessToken); } } catch (Exception ex) @@ -245,6 +234,11 @@ public class HgHookCallbackServlet extends HttpServlet } } + private AuthenticationToken createToken(String tokenString) + { + return BearerToken.valueOf(tokenString); + } + private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type) throws IOException { @@ -259,7 +253,7 @@ public class HgHookCallbackServlet extends HttpServlet File repositoryDirectory = handler.getDirectory(repositoryId); context = new HgHookContextProvider(handler, repositoryDirectory, hookManager, - node, type); + node, type, accessTokenBuilderFactory); hookEventFacade.handle(repositoryId).fireHookEvent(type, context); @@ -460,4 +454,6 @@ public class HgHookCallbackServlet extends HttpServlet /** Field description */ private final HgHookManager hookManager; + + private final AccessTokenBuilderFactory accessTokenBuilderFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java index 3e7e590270..16405ed8bf 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java @@ -1,96 +1,73 @@ package sonia.scm.web; -import com.google.common.base.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import sonia.scm.repository.HgEnvironment; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; +import sonia.scm.security.AccessToken; +import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.security.CipherUtil; -import sonia.scm.util.HttpUtil; -import sonia.scm.web.cgi.EnvList; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import java.io.File; -import java.util.Base64; import java.util.Map; public class HgRepositoryEnvironmentBuilder { - private static final Logger LOG = LoggerFactory.getLogger(HgRepositoryEnvironmentBuilder.class); - private static final String ENV_REPOSITORY_NAME = "REPO_NAME"; private static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH"; private static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID"; private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY"; private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS"; - private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; + private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN"; private final HgRepositoryHandler handler; private final HgHookManager hookManager; + private final AccessTokenBuilderFactory accessTokenBuilderFactory; @Inject - public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager) { + public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.handler = handler; this.hookManager = hookManager; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; } - void buildFor(Repository repository, HttpServletRequest request, EnvList environment) { + public void buildFor(Repository repository, HttpServletRequest request, Map environment) { File directory = handler.getDirectory(repository.getId()); - environment.set(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName()); - environment.set(ENV_REPOSITORY_ID, repository.getId()); - environment.set(ENV_REPOSITORY_PATH, + environment.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName()); + environment.put(ENV_REPOSITORY_ID, repository.getId()); + environment.put(ENV_REPOSITORY_PATH, directory.getAbsolutePath()); // add hook environment - Map environmentMap = environment.asMutableMap(); if (handler.getConfig().isDisableHookSSLValidation()) { // disable ssl validation // Issue 959: https://goo.gl/zH5eY8 - environmentMap.put(ENV_PYTHON_HTTPS_VERIFY, "0"); + environment.put(ENV_PYTHON_HTTPS_VERIFY, "0"); } // enable experimental httppostargs protocol of mercurial // Issue 970: https://goo.gl/poascp - environmentMap.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs())); + environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs())); HgEnvironment.prepareEnvironment( - environmentMap, + environment, handler, hookManager, - request + request, + accessTokenBuilderFactory ); - addCredentials(environment, request); + addCredentials(environment); } - private void addCredentials(EnvList env, HttpServletRequest request) - { - String authorization = request.getHeader(HttpUtil.HEADER_AUTHORIZATION); + private void addCredentials(Map env) { - if (!Strings.isNullOrEmpty(authorization)) - { - if (authorization.startsWith(HttpUtil.AUTHORIZATION_SCHEME_BASIC)) - { - String encodedUserInfo = - authorization.substring( - HttpUtil.AUTHORIZATION_SCHEME_BASIC.length()).trim(); - // TODO check encoding of user-agent ? - String userInfo = new String(Base64.getDecoder().decode(encodedUserInfo)); + AccessToken accessToken = accessTokenBuilderFactory.create().build(); - env.set(SCM_CREDENTIALS, CipherUtil.getInstance().encode(userInfo)); - } - else - { - LOG.warn("unknown authentication scheme used"); - } - } - else - { - LOG.trace("no authorization header found"); - } + String encodedToken = CipherUtil.getInstance().encode(accessToken.compact()); + env.put(SCM_BEARER_TOKEN, encodedToken); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java index b6b085f3ac..0a1531b8a1 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java @@ -50,6 +50,7 @@ import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgPythonScript; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.spi.javahg.HgFileviewExtension; +import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; @@ -58,6 +59,8 @@ import sonia.scm.util.Util; import java.io.File; import java.nio.charset.Charset; +import java.util.Map; +import java.util.function.Consumer; import javax.servlet.http.HttpServletRequest; @@ -102,7 +105,21 @@ public final class HgUtil * @return */ public static Repository open(HgRepositoryHandler handler, - HgHookManager hookManager, File directory, String encoding, boolean pending) + HgHookManager hookManager, File directory, String encoding, boolean pending, + AccessTokenBuilderFactory accessTokenBuilderFactory) + { + return open( + handler, + directory, + encoding, + pending, + environment -> HgEnvironment.prepareEnvironment(environment, handler, hookManager, accessTokenBuilderFactory) + ); + } + + public static Repository open(HgRepositoryHandler handler, + File directory, String encoding, boolean pending, + Consumer> prepareEnvironment) { String enc = encoding; @@ -113,8 +130,7 @@ public final class HgUtil RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT; - HgEnvironment.prepareEnvironment(repoConfiguration.getEnvironment(), - handler, hookManager); + prepareEnvironment.accept(repoConfiguration.getEnvironment()); repoConfiguration.addExtension(HgFileviewExtension.class); repoConfiguration.setEnablePendingChangesets(pending); diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py index 6ad8081512..637aa16331 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py @@ -40,7 +40,7 @@ import os, urllib, urllib2 baseUrl = os.environ['SCM_URL'] challenge = os.environ['SCM_CHALLENGE'] -credentials = os.environ['SCM_CREDENTIALS'] +token = os.environ['SCM_BEARER_TOKEN'] repositoryId = os.environ['SCM_REPOSITORY_ID'] def printMessages(ui, msgs): @@ -54,13 +54,13 @@ def callHookUrl(ui, repo, hooktype, node): try: url = baseUrl + hooktype ui.debug( "send scm-hook to " + url + " and " + node + "\n" ) - data = urllib.urlencode({'node': node, 'challenge': challenge, 'credentials': credentials, 'repositoryPath': repo.root, 'repositoryId': repositoryId}) + data = urllib.urlencode({'node': node, 'challenge': challenge, 'token': token, 'repositoryPath': repo.root, 'repositoryId': repositoryId}) # open url but ignore proxy settings proxy_handler = urllib2.ProxyHandler({}) opener = urllib2.build_opener(proxy_handler) req = urllib2.Request(url, data) conn = opener.open(req) - if conn.code >= 200 and conn.code < 300: + if 200 <= conn.code < 300: ui.debug( "scm-hook " + hooktype + " success with status code " + str(conn.code) + "\n" ) printMessages(ui, conn) abort = False diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java index 2e06d4c6ea..171b917e8e 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java @@ -83,7 +83,7 @@ public class AbstractHgCommandTestBase extends ZippedRepositoryTestBase HgTestUtil.checkForSkip(handler); cmdContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, - RepositoryTestData.createHeartOfGold(), repositoryDirectory); + RepositoryTestData.createHeartOfGold(), repositoryDirectory, null); } //~--- set methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java index 24b262948f..e0756f5293 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -1,18 +1,41 @@ package sonia.scm.repository.spi; +import com.google.inject.util.Providers; import org.assertj.core.api.Assertions; import org.junit.Test; import sonia.scm.repository.Branch; +import sonia.scm.repository.HgHookManager; +import sonia.scm.security.AccessToken; +import sonia.scm.security.AccessTokenBuilder; +import sonia.scm.security.AccessTokenBuilderFactory; +import sonia.scm.web.HgRepositoryEnvironmentBuilder; -import java.io.IOException; import java.util.List; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class HgBranchCommandTest extends AbstractHgCommandTestBase { @Test - public void x() throws IOException { + public void shouldCreateBranch() { Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); - new HgBranchCommand(cmdContext, repository, new SimpleHgWorkdirFactory()).branch("new_branch"); + HgHookManager hookManager = mock(HgHookManager.class); + when(hookManager.getChallenge()).thenReturn("1"); + when(hookManager.createUrl()).thenReturn("http://example.com/"); + AccessTokenBuilderFactory tokenBuilderFactory = mock(AccessTokenBuilderFactory.class); + AccessTokenBuilder tokenBuilder = mock(AccessTokenBuilder.class); + when(tokenBuilderFactory.create()).thenReturn(tokenBuilder); + AccessToken accessToken = mock(AccessToken.class); + when(tokenBuilder.build()).thenReturn(accessToken); + when(accessToken.compact()).thenReturn(""); + + HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder = + new HgRepositoryEnvironmentBuilder(handler, hookManager, tokenBuilderFactory); + + SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder), pc -> {}); + + new HgBranchCommand(cmdContext, repository, workdirFactory).branch("new_branch"); Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java index 4d7b7c75d9..8e374329cb 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java @@ -129,6 +129,6 @@ public class HgIncomingCommandTest extends IncomingOutgoingTestBase return new HgIncomingCommand( new HgCommandContext( HgTestUtil.createHookManager(), handler, incomingRepository, - incomingDirectory), incomingRepository, handler); + incomingDirectory, null), incomingRepository, handler); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java index eae8319b89..71fee6fe84 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java @@ -19,7 +19,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { @Before public void init() { - HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory); + HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory, null); outgoingModificationsCommand = new HgModificationsCommand(outgoingContext, outgoingRepository); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java index 354481b9b3..38f4fa4967 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java @@ -125,6 +125,6 @@ public class HgOutgoingCommandTest extends IncomingOutgoingTestBase return new HgOutgoingCommand( new HgCommandContext( HgTestUtil.createHookManager(), handler, outgoingRepository, - outgoingDirectory), outgoingRepository, handler); + outgoingDirectory, null), outgoingRepository, handler); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java index efe9983951..23de5ee218 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java @@ -20,7 +20,7 @@ public class HgHookCallbackServletTest { @Test public void shouldExtractCorrectRepositoryId() throws ServletException, IOException { HgRepositoryHandler handler = mock(HgRepositoryHandler.class); - HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null); + HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null, null); HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); From 79be188777effc6d961dc929c0545c3083634f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 28 Mar 2019 13:30:04 +0100 Subject: [PATCH 14/31] Clean up dependencies --- .../sonia/scm/repository/HgEnvironment.java | 55 +++++++------------ .../sonia/scm/repository/HgHookManager.java | 21 +++++-- .../scm/repository/spi/HgCommandContext.java | 19 ++----- .../spi/HgHookChangesetProvider.java | 9 +-- .../repository/spi/HgHookContextProvider.java | 5 +- .../spi/HgRepositoryServiceProvider.java | 5 +- .../spi/HgRepositoryServiceResolver.java | 7 +-- .../sonia/scm/web/HgHookCallbackServlet.java | 8 +-- .../web/HgRepositoryEnvironmentBuilder.java | 21 +------ .../src/main/java/sonia/scm/web/HgUtil.java | 6 +- .../java/sonia/scm/repository/HgTestUtil.java | 1 + .../spi/AbstractHgCommandTestBase.java | 5 +- .../repository/spi/HgBranchCommandTest.java | 20 +------ .../repository/spi/HgIncomingCommandTest.java | 2 +- .../spi/HgModificationsCommandTest.java | 2 +- .../repository/spi/HgOutgoingCommandTest.java | 2 +- .../scm/web/HgHookCallbackServletTest.java | 2 +- 17 files changed, 65 insertions(+), 125 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java index c87cc0acea..1ff5c0dabe 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java @@ -35,9 +35,6 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import sonia.scm.security.AccessToken; -import sonia.scm.security.AccessTokenBuilderFactory; -import sonia.scm.security.CipherUtil; import sonia.scm.web.HgUtil; //~--- JDK imports ------------------------------------------------------------ @@ -81,27 +78,11 @@ public final class HgEnvironment * @param environment * @param handler * @param hookManager - * @param request */ public static void prepareEnvironment(Map environment, - HgRepositoryHandler handler, HgHookManager hookManager, - HttpServletRequest request, AccessTokenBuilderFactory accessTokenBuilderFactory) + HgRepositoryHandler handler, HgHookManager hookManager) { - String hookUrl; - - if (request != null) - { - hookUrl = hookManager.createUrl(request); - environment.put(SCM_BEARER_TOKEN, getCredentials(accessTokenBuilderFactory)); - } - else - { - hookUrl = hookManager.createUrl(); - } - - environment.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(handler.getConfig())); - environment.put(ENV_URL, hookUrl); - environment.put(ENV_CHALLENGE, hookManager.getChallenge()); + prepareEnvironment(environment, handler, hookManager, null); } /** @@ -111,25 +92,27 @@ public final class HgEnvironment * @param environment * @param handler * @param hookManager + * @param request */ public static void prepareEnvironment(Map environment, - HgRepositoryHandler handler, HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory) + HgRepositoryHandler handler, HgHookManager hookManager, + HttpServletRequest request) { - prepareEnvironment(environment, handler, hookManager, null, accessTokenBuilderFactory); - } + String hookUrl; - //~--- get methods ---------------------------------------------------------- + if (request != null) + { + hookUrl = hookManager.createUrl(request); + } + else + { + hookUrl = hookManager.createUrl(); + } - /** - * Method description - * - * - * @return - */ - private static String getCredentials(AccessTokenBuilderFactory accessTokenBuilderFactory) - { - AccessToken accessToken = accessTokenBuilderFactory.create().build(); - - return CipherUtil.getInstance().encode(accessToken.compact()); + String credentials = hookManager.getCredentials(); + environment.put(SCM_BEARER_TOKEN, credentials); + environment.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(handler.getConfig())); + environment.put(ENV_URL, hookUrl); + environment.put(ENV_CHALLENGE, hookManager.getChallenge()); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java index 057bd173d9..6815bdad96 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java @@ -47,6 +47,9 @@ import org.slf4j.LoggerFactory; import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfigurationChangedEvent; import sonia.scm.net.ahc.AdvancedHttpClient; +import sonia.scm.security.AccessToken; +import sonia.scm.security.AccessTokenBuilderFactory; +import sonia.scm.security.CipherUtil; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; @@ -78,19 +81,20 @@ public class HgHookManager /** * Constructs ... * - * - * @param configuration + * @param configuration * @param httpServletRequestProvider * @param httpClient + * @param accessTokenBuilderFactory */ @Inject public HgHookManager(ScmConfiguration configuration, - Provider httpServletRequestProvider, - AdvancedHttpClient httpClient) + Provider httpServletRequestProvider, + AdvancedHttpClient httpClient, AccessTokenBuilderFactory accessTokenBuilderFactory) { this.configuration = configuration; this.httpServletRequestProvider = httpServletRequestProvider; this.httpClient = httpClient; + this.accessTokenBuilderFactory = accessTokenBuilderFactory; } //~--- methods -------------------------------------------------------------- @@ -192,6 +196,13 @@ public class HgHookManager return this.challenge.equals(challenge); } + public String getCredentials() + { + AccessToken accessToken = accessTokenBuilderFactory.create().build(); + + return CipherUtil.getInstance().encode(accessToken.compact()); + } + //~--- methods -------------------------------------------------------------- /** @@ -391,4 +402,6 @@ public class HgHookManager /** Field description */ private Provider httpServletRequestProvider; + + private final AccessTokenBuilderFactory accessTokenBuilderFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java index e21a8adca0..c37dc8e4b4 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCommandContext.java @@ -42,7 +42,6 @@ import com.google.common.base.Strings; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.web.HgUtil; //~--- JDK imports ------------------------------------------------------------ @@ -69,34 +68,32 @@ public class HgCommandContext implements Closeable * Constructs ... * * - * @param hookManager + * @param hookManager * @param handler * @param repository * @param directory - * @param accessTokenBuilderFactory */ public HgCommandContext(HgHookManager hookManager, HgRepositoryHandler handler, sonia.scm.repository.Repository repository, - File directory, AccessTokenBuilderFactory accessTokenBuilderFactory) + File directory) { this(hookManager, handler, repository, directory, - handler.getHgContext().isPending(), accessTokenBuilderFactory); + handler.getHgContext().isPending()); } /** * Constructs ... * * - * @param hookManager + * @param hookManager * @param handler * @param repository * @param directory * @param pending - * @param accessTokenBuilderFactory */ public HgCommandContext(HgHookManager hookManager, HgRepositoryHandler handler, sonia.scm.repository.Repository repository, - File directory, boolean pending, AccessTokenBuilderFactory accessTokenBuilderFactory) + File directory, boolean pending) { this.hookManager = hookManager; this.handler = handler; @@ -104,7 +101,6 @@ public class HgCommandContext implements Closeable this.scmRepository = repository; this.encoding = repository.getProperty(PROPERTY_ENCODING); this.pending = pending; - this.accessTokenBuilderFactory = accessTokenBuilderFactory; if (Strings.isNullOrEmpty(encoding)) { @@ -139,8 +135,7 @@ public class HgCommandContext implements Closeable { if (repository == null) { - repository = HgUtil.open(handler, hookManager, directory, encoding, - pending, accessTokenBuilderFactory); + repository = HgUtil.open(handler, hookManager, directory, encoding, pending); } return repository; @@ -186,6 +181,4 @@ public class HgCommandContext implements Closeable private Repository repository; private final sonia.scm.repository.Repository scmRepository; - - private final AccessTokenBuilderFactory accessTokenBuilderFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java index 21d31d9926..404f3a018c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java @@ -40,7 +40,6 @@ import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.spi.javahg.HgLogChangesetCommand; -import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.web.HgUtil; import java.io.File; @@ -64,14 +63,13 @@ public class HgHookChangesetProvider implements HookChangesetProvider public HgHookChangesetProvider(HgRepositoryHandler handler, File repositoryDirectory, HgHookManager hookManager, String startRev, - RepositoryHookType type, AccessTokenBuilderFactory accessTokenBuilderFactory) + RepositoryHookType type) { this.handler = handler; this.repositoryDirectory = repositoryDirectory; this.hookManager = hookManager; this.startRev = startRev; this.type = type; - this.accessTokenBuilderFactory = accessTokenBuilderFactory; } //~--- methods -------------------------------------------------------------- @@ -129,8 +127,7 @@ public class HgHookChangesetProvider implements HookChangesetProvider boolean pending = type == RepositoryHookType.PRE_RECEIVE; // TODO get repository encoding - return HgUtil.open(handler, hookManager, repositoryDirectory, null, - pending, accessTokenBuilderFactory); + return HgUtil.open(handler, hookManager, repositoryDirectory, null, pending); } //~--- fields --------------------------------------------------------------- @@ -152,6 +149,4 @@ public class HgHookChangesetProvider implements HookChangesetProvider /** Field description */ private RepositoryHookType type; - - private final AccessTokenBuilderFactory accessTokenBuilderFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java index df1ee2d501..414cfe27b8 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java @@ -43,7 +43,6 @@ import sonia.scm.repository.api.HookBranchProvider; import sonia.scm.repository.api.HookFeature; import sonia.scm.repository.api.HookMessageProvider; import sonia.scm.repository.api.HookTagProvider; -import sonia.scm.security.AccessTokenBuilderFactory; import java.io.File; import java.util.EnumSet; @@ -76,9 +75,9 @@ public class HgHookContextProvider extends HookContextProvider */ public HgHookContextProvider(HgRepositoryHandler handler, File repositoryDirectory, HgHookManager hookManager, String startRev, - RepositoryHookType type, AccessTokenBuilderFactory accessTokenBuilderFactory) + RepositoryHookType type) { - this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type, accessTokenBuilderFactory); + this.hookChangesetProvider = new HgHookChangesetProvider(handler, repositoryDirectory, hookManager, startRev, type); } //~--- get methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java index 9ae71691aa..d60e888cac 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java @@ -40,7 +40,6 @@ import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.repository.api.Command; import sonia.scm.repository.api.CommandNotSupportedException; -import sonia.scm.security.AccessTokenBuilderFactory; import java.io.File; import java.io.IOException; @@ -78,13 +77,13 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider //~--- constructors --------------------------------------------------------- HgRepositoryServiceProvider(HgRepositoryHandler handler, - HgHookManager hookManager, Repository repository, AccessTokenBuilderFactory accessTokenBuilderFactory) + HgHookManager hookManager, Repository repository) { this.repository = repository; this.handler = handler; this.repositoryDirectory = handler.getDirectory(repository.getId()); this.context = new HgCommandContext(hookManager, handler, repository, - repositoryDirectory, accessTokenBuilderFactory); + repositoryDirectory); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java index 56ce57ffcc..7519cb564d 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java @@ -38,7 +38,6 @@ import sonia.scm.plugin.Extension; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; -import sonia.scm.security.AccessTokenBuilderFactory; /** * @@ -50,15 +49,13 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver private final HgRepositoryHandler handler; private final HgHookManager hookManager; - private final AccessTokenBuilderFactory accessTokenBuilderFactory; @Inject public HgRepositoryServiceResolver(HgRepositoryHandler handler, - HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory) + HgHookManager hookManager) { this.handler = handler; this.hookManager = hookManager; - this.accessTokenBuilderFactory = accessTokenBuilderFactory; } @Override @@ -66,7 +63,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver HgRepositoryServiceProvider provider = null; if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) { - provider = new HgRepositoryServiceProvider(handler, hookManager, repository, accessTokenBuilderFactory); + provider = new HgRepositoryServiceProvider(handler, hookManager, repository); } return provider; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java index 3ab2a06eb3..a2ab84c768 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java @@ -55,7 +55,6 @@ import sonia.scm.repository.api.HgHookMessage; import sonia.scm.repository.api.HgHookMessage.Severity; import sonia.scm.repository.spi.HgHookContextProvider; import sonia.scm.repository.spi.HookEventFacade; -import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.security.BearerToken; import sonia.scm.security.CipherUtil; import sonia.scm.util.HttpUtil; @@ -119,13 +118,12 @@ public class HgHookCallbackServlet extends HttpServlet @Inject public HgHookCallbackServlet(HookEventFacade hookEventFacade, HgRepositoryHandler handler, HgHookManager hookManager, - Provider contextProvider, AccessTokenBuilderFactory accessTokenBuilderFactory) + Provider contextProvider) { this.hookEventFacade = hookEventFacade; this.handler = handler; this.hookManager = hookManager; this.contextProvider = contextProvider; - this.accessTokenBuilderFactory = accessTokenBuilderFactory; } //~--- methods -------------------------------------------------------------- @@ -253,7 +251,7 @@ public class HgHookCallbackServlet extends HttpServlet File repositoryDirectory = handler.getDirectory(repositoryId); context = new HgHookContextProvider(handler, repositoryDirectory, hookManager, - node, type, accessTokenBuilderFactory); + node, type); hookEventFacade.handle(repositoryId).fireHookEvent(type, context); @@ -454,6 +452,4 @@ public class HgHookCallbackServlet extends HttpServlet /** Field description */ private final HgHookManager hookManager; - - private final AccessTokenBuilderFactory accessTokenBuilderFactory; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java index 16405ed8bf..2cb40cbc1b 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgRepositoryEnvironmentBuilder.java @@ -4,9 +4,6 @@ import sonia.scm.repository.HgEnvironment; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; -import sonia.scm.security.AccessToken; -import sonia.scm.security.AccessTokenBuilderFactory; -import sonia.scm.security.CipherUtil; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -20,17 +17,14 @@ public class HgRepositoryEnvironmentBuilder { private static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID"; private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY"; private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS"; - private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN"; private final HgRepositoryHandler handler; private final HgHookManager hookManager; - private final AccessTokenBuilderFactory accessTokenBuilderFactory; @Inject - public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager, AccessTokenBuilderFactory accessTokenBuilderFactory) { + public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager) { this.handler = handler; this.hookManager = hookManager; - this.accessTokenBuilderFactory = accessTokenBuilderFactory; } public void buildFor(Repository repository, HttpServletRequest request, Map environment) { @@ -56,18 +50,7 @@ public class HgRepositoryEnvironmentBuilder { environment, handler, hookManager, - request, - accessTokenBuilderFactory + request ); - - addCredentials(environment); - } - - private void addCredentials(Map env) { - - AccessToken accessToken = accessTokenBuilderFactory.create().build(); - - String encodedToken = CipherUtil.getInstance().encode(accessToken.compact()); - env.put(SCM_BEARER_TOKEN, encodedToken); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java index 0a1531b8a1..427b8179ad 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java @@ -50,7 +50,6 @@ import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgPythonScript; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.spi.javahg.HgFileviewExtension; -import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; @@ -105,15 +104,14 @@ public final class HgUtil * @return */ public static Repository open(HgRepositoryHandler handler, - HgHookManager hookManager, File directory, String encoding, boolean pending, - AccessTokenBuilderFactory accessTokenBuilderFactory) + HgHookManager hookManager, File directory, String encoding, boolean pending) { return open( handler, directory, encoding, pending, - environment -> HgEnvironment.prepareEnvironment(environment, handler, hookManager, accessTokenBuilderFactory) + environment -> HgEnvironment.prepareEnvironment(environment, handler, hookManager) ); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java index 1cecbb21be..cca89d8eb5 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java @@ -128,6 +128,7 @@ public final class HgTestUtil "http://localhost:8081/scm/hook/hg/"); when(hookManager.createUrl(any(HttpServletRequest.class))).thenReturn( "http://localhost:8081/scm/hook/hg/"); + when(hookManager.getCredentials()).thenReturn(""); return hookManager; } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java index 171b917e8e..d752047ba5 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/AbstractHgCommandTestBase.java @@ -40,7 +40,6 @@ import org.junit.Before; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.HgTestUtil; -import sonia.scm.repository.RepositoryPathNotFoundException; import sonia.scm.repository.RepositoryTestData; import sonia.scm.util.MockUtil; @@ -77,13 +76,13 @@ public class AbstractHgCommandTestBase extends ZippedRepositoryTestBase * @throws IOException */ @Before - public void initHgHandler() throws IOException, RepositoryPathNotFoundException { + public void initHgHandler() throws IOException { this.handler = HgTestUtil.createHandler(tempFolder.newFolder()); HgTestUtil.checkForSkip(handler); cmdContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, - RepositoryTestData.createHeartOfGold(), repositoryDirectory, null); + RepositoryTestData.createHeartOfGold(), repositoryDirectory); } //~--- set methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java index e0756f5293..be803d4ef2 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -4,34 +4,18 @@ import com.google.inject.util.Providers; import org.assertj.core.api.Assertions; import org.junit.Test; import sonia.scm.repository.Branch; -import sonia.scm.repository.HgHookManager; -import sonia.scm.security.AccessToken; -import sonia.scm.security.AccessTokenBuilder; -import sonia.scm.security.AccessTokenBuilderFactory; +import sonia.scm.repository.HgTestUtil; import sonia.scm.web.HgRepositoryEnvironmentBuilder; import java.util.List; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - public class HgBranchCommandTest extends AbstractHgCommandTestBase { @Test public void shouldCreateBranch() { Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); - HgHookManager hookManager = mock(HgHookManager.class); - when(hookManager.getChallenge()).thenReturn("1"); - when(hookManager.createUrl()).thenReturn("http://example.com/"); - AccessTokenBuilderFactory tokenBuilderFactory = mock(AccessTokenBuilderFactory.class); - AccessTokenBuilder tokenBuilder = mock(AccessTokenBuilder.class); - when(tokenBuilderFactory.create()).thenReturn(tokenBuilder); - AccessToken accessToken = mock(AccessToken.class); - when(tokenBuilder.build()).thenReturn(accessToken); - when(accessToken.compact()).thenReturn(""); - HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder = - new HgRepositoryEnvironmentBuilder(handler, hookManager, tokenBuilderFactory); + new HgRepositoryEnvironmentBuilder(handler, HgTestUtil.createHookManager()); SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder), pc -> {}); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java index 8e374329cb..4d7b7c75d9 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgIncomingCommandTest.java @@ -129,6 +129,6 @@ public class HgIncomingCommandTest extends IncomingOutgoingTestBase return new HgIncomingCommand( new HgCommandContext( HgTestUtil.createHookManager(), handler, incomingRepository, - incomingDirectory, null), incomingRepository, handler); + incomingDirectory), incomingRepository, handler); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java index 71fee6fe84..eae8319b89 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModificationsCommandTest.java @@ -19,7 +19,7 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase { @Before public void init() { - HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory, null); + HgCommandContext outgoingContext = new HgCommandContext(HgTestUtil.createHookManager(), handler, outgoingRepository, outgoingDirectory); outgoingModificationsCommand = new HgModificationsCommand(outgoingContext, outgoingRepository); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java index 38f4fa4967..354481b9b3 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgOutgoingCommandTest.java @@ -125,6 +125,6 @@ public class HgOutgoingCommandTest extends IncomingOutgoingTestBase return new HgOutgoingCommand( new HgCommandContext( HgTestUtil.createHookManager(), handler, outgoingRepository, - outgoingDirectory, null), outgoingRepository, handler); + outgoingDirectory), outgoingRepository, handler); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java index 23de5ee218..efe9983951 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java @@ -20,7 +20,7 @@ public class HgHookCallbackServletTest { @Test public void shouldExtractCorrectRepositoryId() throws ServletException, IOException { HgRepositoryHandler handler = mock(HgRepositoryHandler.class); - HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null, null); + HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null); HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); From df9e16c485a58013eed5c8c5f9c3202b10ef7bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 28 Mar 2019 14:10:43 +0100 Subject: [PATCH 15/31] Clean up generics --- .../scm/repository/util/CloseableWrapper.java | 2 +- .../repository/util/SimpleWorkdirFactory.java | 23 +++++++++++-------- .../scm/repository/util/WorkdirFactory.java | 4 ++-- .../scm/repository/util/WorkingCopy.java | 6 ++--- .../scm/repository/CloseableWrapperTest.java | 11 +++++---- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java b/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java index 17980ced36..1d353b2f35 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java @@ -2,7 +2,7 @@ package sonia.scm.repository.util; import java.util.function.Consumer; -public class CloseableWrapper implements AutoCloseable { +public class CloseableWrapper implements AutoCloseable { private final T wrapped; private final Consumer cleanup; diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java index 3b761077a2..5f7cbd111c 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -9,28 +9,31 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -public abstract class SimpleWorkdirFactory { +public abstract class SimpleWorkdirFactory implements WorkdirFactory { private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class); private final File poolDirectory; - private final CloneProvider cloneProvider; + private final CloneProvider cloneProvider; - public SimpleWorkdirFactory(CloneProvider cloneProvider) { + public SimpleWorkdirFactory(CloneProvider cloneProvider) { this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool"), cloneProvider); } - public SimpleWorkdirFactory(File poolDirectory, CloneProvider cloneProvider) { + public SimpleWorkdirFactory(File poolDirectory, CloneProvider cloneProvider) { this.poolDirectory = poolDirectory; this.cloneProvider = cloneProvider; - poolDirectory.mkdirs(); + if (!poolDirectory.exists() && !poolDirectory.mkdirs()) { + throw new IllegalStateException("could not create pool directory " + poolDirectory); + } } - public WorkingCopy createWorkingCopy(C context) { + @Override + public WorkingCopy createWorkingCopy(C context) { try { File directory = createNewWorkdir(); - T clone = cloneProvider.cloneRepository(context, directory); + R clone = cloneProvider.cloneRepository(context, directory); return new WorkingCopy<>(clone, this::close, directory); } catch (IOException e) { throw new InternalRepositoryException(getRepository(context), "could not create temporary directory for clone of repository", e); @@ -43,7 +46,7 @@ public abstract class SimpleWorkdirFactory { return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); } - private void close(T repository) { + private void close(R repository) { try { repository.close(); } catch (Exception e) { @@ -51,7 +54,7 @@ public abstract class SimpleWorkdirFactory { } } - public interface CloneProvider { - T cloneRepository(C context, File target) throws IOException; + public interface CloneProvider { + R cloneRepository(C context, File target) throws IOException; } } diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java index 1d3878b250..ff01ae3da6 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java @@ -1,5 +1,5 @@ package sonia.scm.repository.util; -public interface WorkdirFactory { - WorkingCopy createWorkingCopy(C gitContext); +public interface WorkdirFactory { + WorkingCopy createWorkingCopy(C context); } diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java index 6271b8e199..ac949876fb 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java @@ -8,14 +8,14 @@ import java.io.File; import java.io.IOException; import java.util.function.Consumer; -public class WorkingCopy extends CloseableWrapper { +public class WorkingCopy extends CloseableWrapper { private static final Logger LOG = LoggerFactory.getLogger(WorkingCopy.class); private final File directory; - public WorkingCopy(T wrapped, Consumer cleanup, File directory) { - super(wrapped, cleanup); + public WorkingCopy(R wrappedRepository, Consumer cleanup, File directory) { + super(wrappedRepository, cleanup); this.directory = directory; } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java index 3bf0cb8ef7..7c22559f44 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/CloseableWrapperTest.java @@ -12,19 +12,20 @@ public class CloseableWrapperTest { @Test public void shouldExecuteGivenMethodAtClose() { - Consumer wrapped = new Consumer() { + Consumer wrapped = new Consumer() { // no this cannot be replaced with a lambda because otherwise we could not use Mockito#spy @Override - public void accept(String s) { + public void accept(AutoCloseable s) { } }; - Consumer closer = spy(wrapped); + Consumer closer = spy(wrapped); - try (CloseableWrapper wrapper = new CloseableWrapper<>("test", closer)) { + AutoCloseable autoCloseable = () -> {}; + try (CloseableWrapper wrapper = new CloseableWrapper<>(autoCloseable, closer)) { // nothing to do here } - verify(closer).accept("test"); + verify(closer).accept(autoCloseable); } } From 81e493ddf0e3ca3ba6aef87aa41a60e5ff2e44e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 28 Mar 2019 15:10:39 +0100 Subject: [PATCH 16/31] Fetch error when creating token without request --- .../java/sonia/scm/repository/HgEnvironment.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java index 1ff5c0dabe..51ae6d5786 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java @@ -35,6 +35,9 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- +import com.google.inject.ProvisionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sonia.scm.web.HgUtil; //~--- JDK imports ------------------------------------------------------------ @@ -50,6 +53,8 @@ import javax.servlet.http.HttpServletRequest; public final class HgEnvironment { + private static final Logger LOG = LoggerFactory.getLogger(HgEnvironment.class); + /** Field description */ public static final String ENV_PYTHON_PATH = "PYTHONPATH"; @@ -109,8 +114,12 @@ public final class HgEnvironment hookUrl = hookManager.createUrl(); } - String credentials = hookManager.getCredentials(); - environment.put(SCM_BEARER_TOKEN, credentials); + try { + String credentials = hookManager.getCredentials(); + environment.put(SCM_BEARER_TOKEN, credentials); + } catch (ProvisionException e) { + LOG.debug("could not create bearer token; looks like currently we are not in a request", e); + } environment.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(handler.getConfig())); environment.put(ENV_URL, hookUrl); environment.put(ENV_CHALLENGE, hookManager.getChallenge()); From 93cec3d28208c9d568c6b1e77512a85aa96f62f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 28 Mar 2019 16:15:31 +0100 Subject: [PATCH 17/31] Add parameter for parent of new branch --- .../repository/api/BranchCommandBuilder.java | 13 ++++++---- .../scm/repository/api/BranchRequest.java | 22 +++++++++++++++++ .../repository/api/HookChangesetBuilder.java | 5 ++-- .../scm/repository/spi/BranchCommand.java | 3 ++- .../main/java/sonia/scm/web/VndMediaType.java | 1 + .../scm/repository/spi/GitBranchCommand.java | 14 +++++++---- .../repository/spi/GitBranchCommandTest.java | 6 ++++- .../scm/repository/spi/HgBranchCommand.java | 17 +++++++++---- .../repository/spi/HgBranchCommandTest.java | 9 +++++-- .../api/v2/resources/BranchRequestDto.java | 19 +++++++++++++++ .../api/v2/resources/BranchRootResource.java | 24 +++++++++++++------ 11 files changed, 105 insertions(+), 28 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/api/BranchRequest.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRequestDto.java diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java index 09860d1514..aa8485efca 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java @@ -44,17 +44,20 @@ import java.io.IOException; */ public final class BranchCommandBuilder { - private static final Logger LOG = LoggerFactory.getLogger(BranchCommandBuilder.class); - public BranchCommandBuilder(BranchCommand command) { this.command = command; } - public Branch branch(String name) throws IOException { - LOG.debug("branch {}", name); + public BranchCommandBuilder from(String parentBranch) { + request.setParentBranch(parentBranch); + return this; + } - return command.branch(name); + public Branch branch(String name) throws IOException { + request.setNewBranch(name); + return command.branch(request); } private BranchCommand command; + private BranchRequest request = new BranchRequest(); } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchRequest.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchRequest.java new file mode 100644 index 0000000000..8473567156 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchRequest.java @@ -0,0 +1,22 @@ +package sonia.scm.repository.api; + +public class BranchRequest { + private String parentBranch; + private String newBranch; + + public String getParentBranch() { + return parentBranch; + } + + public void setParentBranch(String parentBranch) { + this.parentBranch = parentBranch; + } + + public String getNewBranch() { + return newBranch; + } + + public void setNewBranch(String newBranch) { + this.newBranch = newBranch; + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/HookChangesetBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/HookChangesetBuilder.java index fe43fee038..7e016db430 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/HookChangesetBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/HookChangesetBuilder.java @@ -46,6 +46,7 @@ import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; import sonia.scm.repository.spi.HookChangesetProvider; import sonia.scm.repository.spi.HookChangesetRequest; +import sonia.scm.repository.spi.HookChangesetResponse; //~--- JDK imports ------------------------------------------------------------ @@ -115,8 +116,8 @@ public final class HookChangesetBuilder */ public Iterable getChangesets() { - Iterable changesets = - provider.handleRequest(request).getChangesets(); + HookChangesetResponse hookChangesetResponse = provider.handleRequest(request); + Iterable changesets = hookChangesetResponse.getChangesets(); if (!disablePreProcessors) { diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java index cbce371f4a..181e373382 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java @@ -34,6 +34,7 @@ package sonia.scm.repository.spi; import sonia.scm.repository.Branch; +import sonia.scm.repository.api.BranchRequest; import java.io.IOException; @@ -41,5 +42,5 @@ import java.io.IOException; * @since 2.0 */ public interface BranchCommand { - Branch branch(String name) throws IOException; + Branch branch(BranchRequest name) throws IOException; } diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index d0a64b17ec..4dcb2f1d96 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -27,6 +27,7 @@ public class VndMediaType { public static final String TAG = PREFIX + "tag" + SUFFIX; public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX; public static final String BRANCH = PREFIX + "branch" + SUFFIX; + public static final String BRANCH_REQUEST = PREFIX + "branchRequest" + SUFFIX; public static final String DIFF = PLAIN_TEXT_PREFIX + "diff" + PLAIN_TEXT_SUFFIX; public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX; public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java index 42667f6e51..2040cd24be 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java @@ -42,6 +42,7 @@ import sonia.scm.repository.GitUtil; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; +import sonia.scm.repository.api.BranchRequest; import sonia.scm.repository.util.WorkingCopy; import java.util.stream.StreamSupport; @@ -56,19 +57,22 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman } @Override - public Branch branch(String name) { + public Branch branch(BranchRequest request) { try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { Git clone = new Git(workingCopy.get()); - Ref ref = clone.branchCreate().setName(name).call(); - Iterable call = clone.push().add(name).call(); + if (request.getParentBranch() != null) { + clone.checkout().setName(request.getParentBranch()); + } + Ref ref = clone.branchCreate().setName(request.getNewBranch()).call(); + Iterable call = clone.push().add(request.getNewBranch()).call(); StreamSupport.stream(call.spliterator(), false) .flatMap(pushResult -> pushResult.getRemoteUpdates().stream()) .filter(remoteRefUpdate -> remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) .findFirst() .ifPresent(this::handlePushError); - return Branch.normalBranch(name, GitUtil.getId(ref.getObjectId())); + return Branch.normalBranch(request.getNewBranch(), GitUtil.getId(ref.getObjectId())); } catch (GitAPIException ex) { - throw new InternalRepositoryException(repository, "could not create branch " + name, ex); + throw new InternalRepositoryException(repository, "could not create branch " + request.getNewBranch(), ex); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java index 83f211fed8..364306c033 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchCommandTest.java @@ -4,6 +4,7 @@ import org.assertj.core.api.Assertions; import org.junit.Rule; import org.junit.Test; import sonia.scm.repository.Branch; +import sonia.scm.repository.api.BranchRequest; import java.io.IOException; import java.util.List; @@ -20,7 +21,10 @@ public class GitBranchCommandTest extends AbstractGitCommandTestBase { Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); - new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory()).branch("new_branch"); + BranchRequest branchRequest = new BranchRequest(); + branchRequest.setNewBranch("new_branch"); + + new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory()).branch(branchRequest); Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java index 0d69e5bc75..c4a10ce19f 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java @@ -32,12 +32,16 @@ package sonia.scm.repository.spi; import com.aragost.javahg.Changeset; import com.aragost.javahg.commands.CommitCommand; +import com.aragost.javahg.commands.UpdateCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.Repository; +import sonia.scm.repository.api.BranchRequest; import sonia.scm.repository.util.WorkingCopy; +import java.io.IOException; + /** * Mercurial implementation of the {@link BranchCommand}. * Note that this creates an empty commit to "persist" the new branch. @@ -54,21 +58,24 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { } @Override - public Branch branch(String name) { + public Branch branch(BranchRequest request) throws IOException { try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext())) { com.aragost.javahg.Repository repository = workingCopy.get().get(); - com.aragost.javahg.commands.BranchCommand.on(repository).set(name); + if (request.getParentBranch() != null) { + UpdateCommand.on(repository).rev(request.getParentBranch()).execute(); + } + com.aragost.javahg.commands.BranchCommand.on(repository).set(request.getNewBranch()); Changeset emptyChangeset = CommitCommand .on(repository) .user("SCM-Manager") - .message("Create new branch " + name) + .message("Create new branch " + request.getNewBranch()) .execute(); LOG.debug("Created new branch '{}' in repository {} with changeset {}", - name, getRepository().getNamespaceAndName(), emptyChangeset.getNode()); + request.getNewBranch(), getRepository().getNamespaceAndName(), emptyChangeset.getNode()); - return Branch.normalBranch(name, emptyChangeset.getNode()); + return Branch.normalBranch(request.getNewBranch(), emptyChangeset.getNode()); } } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java index be803d4ef2..34f1f5994d 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -5,13 +5,15 @@ import org.assertj.core.api.Assertions; import org.junit.Test; import sonia.scm.repository.Branch; import sonia.scm.repository.HgTestUtil; +import sonia.scm.repository.api.BranchRequest; import sonia.scm.web.HgRepositoryEnvironmentBuilder; +import java.io.IOException; import java.util.List; public class HgBranchCommandTest extends AbstractHgCommandTestBase { @Test - public void shouldCreateBranch() { + public void shouldCreateBranch() throws IOException { Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isEmpty(); HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder = @@ -19,7 +21,10 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase { SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder), pc -> {}); - new HgBranchCommand(cmdContext, repository, workdirFactory).branch("new_branch"); + BranchRequest branchRequest = new BranchRequest(); + branchRequest.setNewBranch("new_branch"); + + new HgBranchCommand(cmdContext, repository, workdirFactory).branch(branchRequest); Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty(); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRequestDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRequestDto.java new file mode 100644 index 0000000000..3db338ea85 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRequestDto.java @@ -0,0 +1,19 @@ +package sonia.scm.api.v2.resources; + +import lombok.Getter; +import lombok.Setter; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.constraints.Pattern; + +import static sonia.scm.api.v2.resources.BranchDto.VALID_BRANCH_NAMES; + +@Getter +@Setter +public class BranchRequestDto { + + @NotEmpty @Length(min = 1, max=100) @Pattern(regexp = VALID_BRANCH_NAMES) + private String name; + private String parent; +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java index b35b59beca..f0edbf79fb 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java @@ -1,5 +1,6 @@ package sonia.scm.api.v2.resources; +import com.google.common.base.Strings; import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.ResponseHeader; import com.webcohesion.enunciate.metadata.rs.ResponseHeaders; @@ -13,6 +14,7 @@ import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryPermissions; +import sonia.scm.repository.api.BranchCommandBuilder; import sonia.scm.repository.api.CommandNotSupportedException; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -132,14 +134,14 @@ public class BranchRootResource { /** * Creates a new branch. * - * @param namespace the namespace of the repository - * @param name the name of the repository - * @param branchName The branch to be created. + * @param namespace the namespace of the repository + * @param name the name of the repository + * @param branchRequest the request giving the name of the new branch and an optional parent branch * @return A response with the link to the new branch (if created successfully). */ @POST @Path("") - @Consumes(VndMediaType.BRANCH) + @Consumes(VndMediaType.BRANCH_REQUEST) @StatusCodes({ @ResponseCode(code = 201, condition = "create success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), @@ -151,16 +153,24 @@ public class BranchRootResource { @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created branch")) public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name, - @Valid BranchDto branchToCreate) throws IOException { + @Valid BranchRequestDto branchRequest) throws IOException { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); - String branchName = branchToCreate.getName(); + String branchName = branchRequest.getName(); + String parentName = branchRequest.getParent(); try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) { if (branchExists(branchName, repositoryService)) { throw alreadyExists(entity(Branch.class, branchName).in(Repository.class, namespace + "/" + name)); } Repository repository = repositoryService.getRepository(); RepositoryPermissions.push(repository).check(); - Branch newBranch = repositoryService.getBranchCommand().branch(branchName); + BranchCommandBuilder branchCommand = repositoryService.getBranchCommand(); + if (!Strings.isNullOrEmpty(parentName)) { + if (!branchExists(parentName, repositoryService)) { + throw notFound(entity(Branch.class, parentName).in(Repository.class, namespace + "/" + name)); + } + branchCommand.from(parentName); + } + Branch newBranch = branchCommand.branch(branchName); return Response.created(URI.create(resourceLinks.branch().self(namespaceAndName, newBranch.getName()))).build(); } } From f010fb77115c3287ce933376e13fdcdf98ae82e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 28 Mar 2019 16:57:46 +0100 Subject: [PATCH 18/31] Fix unit test --- .../v2/resources/BranchRootResourceTest.java | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java index eae9d36082..f0e7536a22 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java @@ -43,6 +43,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) @@ -52,7 +54,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { public static final String BRANCH_PATH = "space/repo/branches/master"; public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH; public static final String REVISION = "revision"; - private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private Dispatcher dispatcher; private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -99,7 +101,9 @@ public class BranchRootResourceTest extends RepositoryTestBase { BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks); branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper, resourceLinks); super.branchRootResource = Providers.of(branchRootResource); - dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); + dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); +// dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); + when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); @@ -180,7 +184,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { MockHttpRequest request = MockHttpRequest .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/") .content("{\"name\": \"new_branch\"}".getBytes()) - .contentType(VndMediaType.BRANCH); + .contentType(VndMediaType.BRANCH_REQUEST); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -192,14 +196,15 @@ public class BranchRootResourceTest extends RepositoryTestBase { } @Test - public void shouldNotCreateExistingBranchAgain() throws Exception { + public void shouldCreateNewBranchWithParent() throws Exception { when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(createBranch("existing_branch"))); + when(branchCommandBuilder.from("existing_branch")).thenReturn(branchCommandBuilder); when(branchCommandBuilder.branch("new_branch")).thenReturn(createBranch("new_branch")); MockHttpRequest request = MockHttpRequest .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/") - .content("{\"name\": \"new_branch\"}".getBytes()) - .contentType(VndMediaType.BRANCH); + .content("{\"name\": \"new_branch\",\"parent\": \"existing_branch\"}".getBytes()) + .contentType(VndMediaType.BRANCH_REQUEST); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -208,6 +213,39 @@ public class BranchRootResourceTest extends RepositoryTestBase { assertEquals( URI.create("/v2/repositories/space/repo/branches/new_branch"), response.getOutputHeaders().getFirst("Location")); + verify(branchCommandBuilder).from("existing_branch"); + } + + @Test + public void shouldNotCreateExistingBranchAgain() throws Exception { + when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(createBranch("existing_branch"))); + + MockHttpRequest request = MockHttpRequest + .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/") + .content("{\"name\": \"existing_branch\"}".getBytes()) + .contentType(VndMediaType.BRANCH_REQUEST); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(409, response.getStatus()); + verify(branchCommandBuilder, never()).branch(anyString()); + } + + @Test + public void shouldFailForMissingParentBranch() throws Exception { + when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(createBranch("existing_branch"))); + + MockHttpRequest request = MockHttpRequest + .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/") + .content("{\"name\": \"new_branch\",\"parent\": \"no_such_branch\"}".getBytes()) + .contentType(VndMediaType.BRANCH_REQUEST); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(404, response.getStatus()); + verify(branchCommandBuilder, never()).branch(anyString()); } private Branch createBranch(String existing_branch) { From 1162536e212c5600215f36690b8a2872c8102f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 29 Mar 2019 10:02:38 +0100 Subject: [PATCH 19/31] Simplify workdir factory --- .../repository/util/SimpleWorkdirFactory.java | 33 ++++++++-- .../scm/repository/util/WorkdirFactory.java | 2 +- .../scm/repository/util/WorkingCopy.java | 22 +++++-- .../scm/repository/spi/GitBranchCommand.java | 2 +- .../scm/repository/spi/GitMergeCommand.java | 2 +- .../spi/SimpleGitWorkdirFactory.java | 11 +++- .../spi/SimpleGitWorkdirFactoryTest.java | 11 ++-- .../scm/repository/spi/HgBranchCommand.java | 13 +++- .../scm/repository/spi/HgWorkdirFactory.java | 5 +- .../spi/SimpleHgWorkdirFactory.java | 61 +++++-------------- .../repository/spi/HgBranchCommandTest.java | 2 +- 11 files changed, 91 insertions(+), 73 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java index 5f7cbd111c..37ba1874db 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -9,7 +9,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -public abstract class SimpleWorkdirFactory implements WorkdirFactory { +public abstract class SimpleWorkdirFactory implements WorkdirFactory { private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class); @@ -33,8 +33,8 @@ public abstract class SimpleWorkdirFactory implement public WorkingCopy createWorkingCopy(C context) { try { File directory = createNewWorkdir(); - R clone = cloneProvider.cloneRepository(context, directory); - return new WorkingCopy<>(clone, this::close, directory); + ParentAndClone parentAndClone = cloneProvider.cloneRepository(context, directory); + return new WorkingCopy<>(parentAndClone.getClone(), parentAndClone.getParent(), this::close, directory); } catch (IOException e) { throw new InternalRepositoryException(getRepository(context), "could not create temporary directory for clone of repository", e); } @@ -42,19 +42,40 @@ public abstract class SimpleWorkdirFactory implement protected abstract Repository getRepository(C context); + protected abstract void closeRepository(R repository); + private File createNewWorkdir() throws IOException { return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); } private void close(R repository) { try { - repository.close(); + closeRepository(repository); } catch (Exception e) { logger.warn("could not close temporary repository clone", e); } } - public interface CloneProvider { - R cloneRepository(C context, File target) throws IOException; + @FunctionalInterface + protected interface CloneProvider { + ParentAndClone cloneRepository(C context, File target) throws IOException; + } + + protected static class ParentAndClone { + private final R parent; + private final R clone; + + public ParentAndClone(R parent, R clone) { + this.parent = parent; + this.clone = clone; + } + + public R getParent() { + return parent; + } + + public R getClone() { + return clone; + } } } diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java index ff01ae3da6..1b67c7f1eb 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkdirFactory.java @@ -1,5 +1,5 @@ package sonia.scm.repository.util; -public interface WorkdirFactory { +public interface WorkdirFactory { WorkingCopy createWorkingCopy(C context); } diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java index ac949876fb..353fca30d2 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java @@ -8,20 +8,34 @@ import java.io.File; import java.io.IOException; import java.util.function.Consumer; -public class WorkingCopy extends CloseableWrapper { +public class WorkingCopy implements AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(WorkingCopy.class); private final File directory; + private final R workingRepository; + private final R centralRepository; - public WorkingCopy(R wrappedRepository, Consumer cleanup, File directory) { - super(wrappedRepository, cleanup); + public WorkingCopy(R workingRepository, R centralRepository, Consumer cleanup, File directory) { this.directory = directory; + this.workingRepository = workingRepository; + this.centralRepository = centralRepository; + } + + public R getWorkingRepository() { + return workingRepository; + } + + public R getCentralRepository() { + return centralRepository; + } + + public File getDirectory() { + return directory; } @Override public void close() { - super.close(); try { IOUtil.delete(directory); } catch (IOException e) { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java index 2040cd24be..787cfb78bf 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java @@ -59,7 +59,7 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman @Override public Branch branch(BranchRequest request) { try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { - Git clone = new Git(workingCopy.get()); + Git clone = new Git(workingCopy.getWorkingRepository()); if (request.getParentBranch() != null) { clone.checkout().setName(request.getParentBranch()); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index f402d63c34..6f4d09e4f6 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -50,7 +50,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand RepositoryPermissions.push(context.getRepository().getId()).check(); try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { - Repository repository = workingCopy.get(); + Repository repository = workingCopy.getWorkingRepository(); logger.debug("cloned repository to folder {}", repository.getWorkTree()); return new MergeWorker(repository, request).merge(); } catch (IOException e) { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java index d9fed92968..1346504b12 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java @@ -23,13 +23,13 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory { @Override - public Repository cloneRepository(GitContext context, File target) { + public ParentAndClone cloneRepository(GitContext context, File target) { try { - return Git.cloneRepository() + return new ParentAndClone<>(null, Git.cloneRepository() .setURI(createScmTransportProtocolUri(context.getDirectory())) .setDirectory(target) .call() - .getRepository(); + .getRepository()); } catch (GitAPIException e) { throw new InternalRepositoryException(context.getRepository(), "could not clone working copy of repository", e); } @@ -40,6 +40,11 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory workingCopy = factory.createWorkingCopy(createContext())) { - assertThat(workingCopy.get().getDirectory()) + assertThat(workingCopy.getDirectory()) .exists() .isNotEqualTo(masterRepo) .isDirectory(); - assertThat(new File(workingCopy.get().getWorkTree(), "a.txt")) + assertThat(new File(workingCopy.getWorkingRepository().getWorkTree(), "a.txt")) .exists() .isFile() .hasContent("a\nline for blame"); @@ -64,10 +63,10 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { File firstDirectory; try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { - firstDirectory = workingCopy.get().getDirectory(); + firstDirectory = workingCopy.getDirectory(); } try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { - File secondDirectory = workingCopy.get().getDirectory(); + File secondDirectory = workingCopy.getDirectory(); assertThat(secondDirectory).isNotEqualTo(firstDirectory); } } @@ -78,7 +77,7 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase { File directory; try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) { - directory = workingCopy.get().getWorkTree(); + directory = workingCopy.getWorkingRepository().getWorkTree(); } assertThat(directory).doesNotExist(); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java index c4a10ce19f..24fb88ce50 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java @@ -32,6 +32,7 @@ package sonia.scm.repository.spi; import com.aragost.javahg.Changeset; import com.aragost.javahg.commands.CommitCommand; +import com.aragost.javahg.commands.PullCommand; import com.aragost.javahg.commands.UpdateCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,8 +60,8 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { @Override public Branch branch(BranchRequest request) throws IOException { - try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext())) { - com.aragost.javahg.Repository repository = workingCopy.get().get(); + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext())) { + com.aragost.javahg.Repository repository = workingCopy.getWorkingRepository(); if (request.getParentBranch() != null) { UpdateCommand.on(repository).rev(request.getParentBranch()).execute(); } @@ -75,6 +76,14 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { LOG.debug("Created new branch '{}' in repository {} with changeset {}", request.getNewBranch(), getRepository().getNamespaceAndName(), emptyChangeset.getNode()); + try { + com.aragost.javahg.commands.PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository()); + workdirFactory.configure(pullCommand); + pullCommand.execute(workingCopy.getDirectory().getAbsolutePath()); + } catch (Exception e) { + throw new RuntimeException(e); + } + return Branch.normalBranch(request.getNewBranch(), emptyChangeset.getNode()); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java index b32b485cca..515ee62c98 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgWorkdirFactory.java @@ -1,6 +1,9 @@ package sonia.scm.repository.spi; +import com.aragost.javahg.Repository; +import com.aragost.javahg.commands.PullCommand; import sonia.scm.repository.util.WorkdirFactory; -public interface HgWorkdirFactory extends WorkdirFactory { +public interface HgWorkdirFactory extends WorkdirFactory { + void configure(PullCommand pullCommand); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java index 2abf4253ff..8bf4bd2a3c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/SimpleHgWorkdirFactory.java @@ -12,77 +12,44 @@ import java.io.File; import java.io.IOException; import java.util.Map; import java.util.function.BiConsumer; -import java.util.function.Consumer; -public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory implements HgWorkdirFactory { +public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory implements HgWorkdirFactory { @Inject public SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder) { - this(hgRepositoryEnvironmentBuilder, new HookConfigurer()); + super(new HgCloneProvider(hgRepositoryEnvironmentBuilder)); } - SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder, Consumer hookConfigurer) { - super(new HgCloneProvider(hgRepositoryEnvironmentBuilder, hookConfigurer)); - } - - private static class HgCloneProvider implements CloneProvider { + private static class HgCloneProvider implements CloneProvider { private final Provider hgRepositoryEnvironmentBuilder; - private final Consumer hookConfigurer; - private HgCloneProvider(Provider hgRepositoryEnvironmentBuilder, Consumer hookConfigurer) { + private HgCloneProvider(Provider hgRepositoryEnvironmentBuilder) { this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder; - this.hookConfigurer = hookConfigurer; } @Override - public RepositoryCloseableWrapper cloneRepository(HgCommandContext context, File target) throws IOException { - BiConsumer> repositoryMapBiConsumer = (repository, environment) -> { - hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment); - }; + public ParentAndClone cloneRepository(HgCommandContext context, File target) throws IOException { + BiConsumer> repositoryMapBiConsumer = + (repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment); Repository centralRepository = context.openWithSpecialEnvironment(repositoryMapBiConsumer); CloneCommand.on(centralRepository).execute(target.getAbsolutePath()); - return new RepositoryCloseableWrapper(Repository.open(target), centralRepository, hookConfigurer); + return new ParentAndClone<>(centralRepository, Repository.open(target)); } } + @Override + protected void closeRepository(Repository repository) { + repository.close(); + } + @Override protected sonia.scm.repository.Repository getRepository(HgCommandContext context) { return null; } -} - -class RepositoryCloseableWrapper implements AutoCloseable { - private final Repository delegate; - private final Repository centralRepository; - private final Consumer hookConfigurer; - - RepositoryCloseableWrapper(Repository delegate, Repository centralRepository, Consumer hookConfigurer) { - this.delegate = delegate; - this.centralRepository = centralRepository; - this.hookConfigurer = hookConfigurer; - } - - Repository get() { - return delegate; - } @Override - public void close() { - try { - PullCommand pullCommand = PullCommand.on(centralRepository); - hookConfigurer.accept(pullCommand); - pullCommand.execute(delegate.getDirectory().getAbsolutePath()); - } catch (Exception e) { - throw new RuntimeException(e); - } - centralRepository.close(); - } -} - -class HookConfigurer implements Consumer { - @Override - public void accept(PullCommand pullCommand) { + public void configure(PullCommand pullCommand) { pullCommand.cmdAppend("--config", "hooks.changegroup.scm=python:scmhooks.postHook"); pullCommand.cmdAppend("--config", "hooks.pretxnchangegroup.scm=python:scmhooks.preHook"); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java index 34f1f5994d..9f8fb1c792 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchCommandTest.java @@ -19,7 +19,7 @@ public class HgBranchCommandTest extends AbstractHgCommandTestBase { HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder = new HgRepositoryEnvironmentBuilder(handler, HgTestUtil.createHookManager()); - SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder), pc -> {}); + SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder)); BranchRequest branchRequest = new BranchRequest(); branchRequest.setNewBranch("new_branch"); From ee219f2d5951458ef0659ea121fbff452a5fb65d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 29 Mar 2019 10:36:55 +0100 Subject: [PATCH 20/31] Remove unnecessary interface --- .../repository/util/SimpleWorkdirFactory.java | 22 +++++------- .../spi/SimpleGitWorkdirFactory.java | 36 +++++++++---------- .../scm/repository/spi/HgCommandContext.java | 4 +++ .../spi/SimpleHgWorkdirFactory.java | 32 +++++++---------- .../repository/spi/HgBranchCommandTest.java | 8 ++++- 5 files changed, 47 insertions(+), 55 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java index 37ba1874db..de4ae6069d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -15,15 +15,12 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory private final File poolDirectory; - private final CloneProvider cloneProvider; - - public SimpleWorkdirFactory(CloneProvider cloneProvider) { - this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool"), cloneProvider); + public SimpleWorkdirFactory() { + this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool")); } - public SimpleWorkdirFactory(File poolDirectory, CloneProvider cloneProvider) { + public SimpleWorkdirFactory(File poolDirectory) { this.poolDirectory = poolDirectory; - this.cloneProvider = cloneProvider; if (!poolDirectory.exists() && !poolDirectory.mkdirs()) { throw new IllegalStateException("could not create pool directory " + poolDirectory); } @@ -33,17 +30,19 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory public WorkingCopy createWorkingCopy(C context) { try { File directory = createNewWorkdir(); - ParentAndClone parentAndClone = cloneProvider.cloneRepository(context, directory); + ParentAndClone parentAndClone = cloneRepository(context, directory); return new WorkingCopy<>(parentAndClone.getClone(), parentAndClone.getParent(), this::close, directory); } catch (IOException e) { - throw new InternalRepositoryException(getRepository(context), "could not create temporary directory for clone of repository", e); + throw new InternalRepositoryException(getScmRepository(context), "could not clone repository in temporary directory", e); } } - protected abstract Repository getRepository(C context); + protected abstract Repository getScmRepository(C context); protected abstract void closeRepository(R repository); + protected abstract ParentAndClone cloneRepository(C context, File target) throws IOException; + private File createNewWorkdir() throws IOException { return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile(); } @@ -56,11 +55,6 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory } } - @FunctionalInterface - protected interface CloneProvider { - ParentAndClone cloneRepository(C context, File target) throws IOException; - } - protected static class ParentAndClone { private final R parent; private final R clone; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java index 1346504b12..a4d1ab830e 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java @@ -13,31 +13,27 @@ import java.io.File; public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements GitWorkdirFactory { public SimpleGitWorkdirFactory() { - super(new GitCloneProvider()); } - public SimpleGitWorkdirFactory(File poolDirectory) { - super(poolDirectory, new GitCloneProvider()); + SimpleGitWorkdirFactory(File poolDirectory) { + super(poolDirectory); } - private static class GitCloneProvider implements CloneProvider { - - @Override - public ParentAndClone cloneRepository(GitContext context, File target) { - try { - return new ParentAndClone<>(null, Git.cloneRepository() - .setURI(createScmTransportProtocolUri(context.getDirectory())) - .setDirectory(target) - .call() - .getRepository()); - } catch (GitAPIException e) { - throw new InternalRepositoryException(context.getRepository(), "could not clone working copy of repository", e); - } + @Override + public ParentAndClone cloneRepository(GitContext context, File target) { + try { + return new ParentAndClone<>(null, Git.cloneRepository() + .setURI(createScmTransportProtocolUri(context.getDirectory())) + .setDirectory(target) + .call() + .getRepository()); + } catch (GitAPIException e) { + throw new InternalRepositoryException(context.getRepository(), "could not clone working copy of repository", e); } + } - private String createScmTransportProtocolUri(File bareRepository) { - return ScmTransportProtocol.NAME + "://" + bareRepository.getAbsolutePath(); - } + private String createScmTransportProtocolUri(File bareRepository) { + return ScmTransportProtocol.NAME + "://" + bareRepository.getAbsolutePath(); } @Override @@ -46,7 +42,7 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory implements HgWorkdirFactory { + private final Provider hgRepositoryEnvironmentBuilder; + @Inject public SimpleHgWorkdirFactory(Provider hgRepositoryEnvironmentBuilder) { - super(new HgCloneProvider(hgRepositoryEnvironmentBuilder)); + this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder; } - - private static class HgCloneProvider implements CloneProvider { - - private final Provider hgRepositoryEnvironmentBuilder; - - private HgCloneProvider(Provider hgRepositoryEnvironmentBuilder) { - this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder; - } - - @Override - public ParentAndClone cloneRepository(HgCommandContext context, File target) throws IOException { - BiConsumer> repositoryMapBiConsumer = - (repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment); - Repository centralRepository = context.openWithSpecialEnvironment(repositoryMapBiConsumer); - CloneCommand.on(centralRepository).execute(target.getAbsolutePath()); - return new ParentAndClone<>(centralRepository, Repository.open(target)); - } + @Override + public ParentAndClone cloneRepository(HgCommandContext context, File target) throws IOException { + BiConsumer> repositoryMapBiConsumer = + (repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment); + Repository centralRepository = context.openWithSpecialEnvironment(repositoryMapBiConsumer); + CloneCommand.on(centralRepository).execute(target.getAbsolutePath()); + return new ParentAndClone<>(centralRepository, Repository.open(target)); } @Override @@ -44,8 +36,8 @@ public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory Date: Fri, 29 Mar 2019 11:06:10 +0100 Subject: [PATCH 21/31] Remove unknown val --- .../main/java/sonia/scm/repository/spi/GitBranchesCommand.java | 3 +-- .../java/sonia/scm/repository/spi/GitBranchesCommandTest.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java index 81b38cab5c..ed0ac772f1 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java @@ -37,7 +37,6 @@ package sonia.scm.repository.spi; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.common.value.qual.UnknownVal; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; @@ -118,8 +117,8 @@ public class GitBranchesCommand extends AbstractGitCommand implements BranchesCo } } - @UnknownVal Optional getRepositoryHeadRef(Git git) { return GitUtil.getRepositoryHeadRef(git.getRepository()); } } + diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java index af765bc7aa..1c737deab8 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchesCommandTest.java @@ -1,6 +1,5 @@ package sonia.scm.repository.spi; -import org.checkerframework.common.value.qual.UnknownVal; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ListBranchCommand; import org.eclipse.jgit.api.errors.GitAPIException; @@ -57,7 +56,7 @@ class GitBranchesCommandTest { } @Override - @UnknownVal Optional getRepositoryHeadRef(Git git) { + Optional getRepositoryHeadRef(Git git) { return of(master); } }; From ada8977175c03d7e091ca3c5042af36000035ab7 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 29 Mar 2019 10:23:56 +0000 Subject: [PATCH 22/31] Close branch feature/mark_default_branch From 00b27b548835e599b781d47b6b2c784056ba34a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 29 Mar 2019 11:47:44 +0100 Subject: [PATCH 23/31] Close repositories after usage --- .../repository/util/SimpleWorkdirFactory.java | 4 +- .../scm/repository/util/WorkingCopy.java | 4 + .../util/SimpleWorkdirFactoryTest.java | 75 +++++++++++++++++++ .../spi/SimpleGitWorkdirFactory.java | 6 +- 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java index de4ae6069d..9642f72ae7 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -39,7 +39,9 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory protected abstract Repository getScmRepository(C context); - protected abstract void closeRepository(R repository); + @SuppressWarnings("squid:S00112") + // We do allow implementations to throw arbitrary exceptions here, so that we can handle them in close + protected abstract void closeRepository(R repository) throws Exception; protected abstract ParentAndClone cloneRepository(C context, File target) throws IOException; diff --git a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java index 353fca30d2..3c96184142 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/WorkingCopy.java @@ -15,11 +15,13 @@ public class WorkingCopy implements AutoCloseable { private final File directory; private final R workingRepository; private final R centralRepository; + private final Consumer cleanup; public WorkingCopy(R workingRepository, R centralRepository, Consumer cleanup, File directory) { this.directory = directory; this.workingRepository = workingRepository; this.centralRepository = centralRepository; + this.cleanup = cleanup; } public R getWorkingRepository() { @@ -37,6 +39,8 @@ public class WorkingCopy implements AutoCloseable { @Override public void close() { try { + cleanup.accept(workingRepository); + cleanup.accept(centralRepository); IOUtil.delete(directory); } catch (IOException e) { LOG.warn("could not delete temporary workdir '{}'", directory, e); diff --git a/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java b/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java new file mode 100644 index 0000000000..2d3c2ed59e --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/repository/util/SimpleWorkdirFactoryTest.java @@ -0,0 +1,75 @@ +package sonia.scm.repository.util; + + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import sonia.scm.repository.Repository; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class SimpleWorkdirFactoryTest { + + private static final Repository REPOSITORY = new Repository("1", "git", "space", "X"); + + private final Closeable parent = mock(Closeable.class); + private final Closeable clone = mock(Closeable.class); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + private SimpleWorkdirFactory simpleWorkdirFactory; + + @Before + public void initFactory() throws IOException { + simpleWorkdirFactory = new SimpleWorkdirFactory(temporaryFolder.newFolder()) { + @Override + protected Repository getScmRepository(Context context) { + return REPOSITORY; + } + + @Override + protected void closeRepository(Closeable repository) throws IOException { + repository.close(); + } + + @Override + protected ParentAndClone cloneRepository(Context context, File target) { + return new ParentAndClone<>(parent, clone); + } + }; + } + + @Test + public void shouldCreateParentAndClone() { + Context context = new Context(); + try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context)) { + assertThat(workingCopy.getCentralRepository()).isSameAs(parent); + assertThat(workingCopy.getWorkingRepository()).isSameAs(clone); + } + } + + @Test + public void shouldCloseParent() throws IOException { + Context context = new Context(); + try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context)) {} + + verify(parent).close(); + } + + @Test + public void shouldCloseClone() throws IOException { + Context context = new Context(); + try (WorkingCopy workingCopy = simpleWorkdirFactory.createWorkingCopy(context)) {} + + verify(clone).close(); + } + + private static class Context {} +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java index a4d1ab830e..c30ff59afb 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/SimpleGitWorkdirFactory.java @@ -38,7 +38,11 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory Date: Fri, 29 Mar 2019 13:25:22 +0100 Subject: [PATCH 24/31] Minimal cleanup --- .../sonia/scm/repository/spi/HgHookChangesetProvider.java | 7 ++++--- .../scm/repository/spi/HgRepositoryServiceResolver.java | 6 +++--- .../sonia/scm/api/v2/resources/BranchRootResourceTest.java | 2 -- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java index 404f3a018c..e4e3dc238e 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java @@ -62,8 +62,8 @@ public class HgHookChangesetProvider implements HookChangesetProvider //~--- constructors --------------------------------------------------------- public HgHookChangesetProvider(HgRepositoryHandler handler, - File repositoryDirectory, HgHookManager hookManager, String startRev, - RepositoryHookType type) + File repositoryDirectory, HgHookManager hookManager, String startRev, + RepositoryHookType type) { this.handler = handler; this.repositoryDirectory = repositoryDirectory; @@ -127,7 +127,8 @@ public class HgHookChangesetProvider implements HookChangesetProvider boolean pending = type == RepositoryHookType.PRE_RECEIVE; // TODO get repository encoding - return HgUtil.open(handler, hookManager, repositoryDirectory, null, pending); + return HgUtil.open(handler, hookManager, repositoryDirectory, null, + pending); } //~--- fields --------------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java index 7519cb564d..d6d04ee017 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceResolver.java @@ -47,12 +47,12 @@ import sonia.scm.repository.Repository; public class HgRepositoryServiceResolver implements RepositoryServiceResolver { - private final HgRepositoryHandler handler; - private final HgHookManager hookManager; + private HgRepositoryHandler handler; + private HgHookManager hookManager; @Inject public HgRepositoryServiceResolver(HgRepositoryHandler handler, - HgHookManager hookManager) + HgHookManager hookManager) { this.handler = handler; this.hookManager = hookManager; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java index f0e7536a22..9fb20c0922 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java @@ -102,8 +102,6 @@ public class BranchRootResourceTest extends RepositoryTestBase { branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper, resourceLinks); super.branchRootResource = Providers.of(branchRootResource); dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); -// dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); - when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); From 89de300137a04ee7935264fba042c8016573d486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 29 Mar 2019 13:39:55 +0100 Subject: [PATCH 25/31] Add minimal error handling --- .../sonia/scm/repository/spi/GitBranchCommand.java | 13 ++++++++++--- .../sonia/scm/repository/spi/HgBranchCommand.java | 7 ++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java index 787cfb78bf..208b534343 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java @@ -69,14 +69,21 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman .flatMap(pushResult -> pushResult.getRemoteUpdates().stream()) .filter(remoteRefUpdate -> remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) .findFirst() - .ifPresent(this::handlePushError); + .ifPresent(r -> this.handlePushError(r, request, context.getRepository())); return Branch.normalBranch(request.getNewBranch(), GitUtil.getId(ref.getObjectId())); } catch (GitAPIException ex) { throw new InternalRepositoryException(repository, "could not create branch " + request.getNewBranch(), ex); } } - private void handlePushError(RemoteRefUpdate remoteRefUpdate) { - // TODO handle failed remote update + private void handlePushError(RemoteRefUpdate remoteRefUpdate, BranchRequest request, Repository repository) { + if (remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) { + // TODO handle failed remote update + throw new RuntimeException( + String.format("Could not pull new branch '%s' into central repository '%s': %s", + request.getNewBranch(), + repository.getNamespaceAndName(), + remoteRefUpdate.getMessage())); + } } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java index 24fb88ce50..5a0b6d0cd1 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java @@ -81,7 +81,12 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { workdirFactory.configure(pullCommand); pullCommand.execute(workingCopy.getDirectory().getAbsolutePath()); } catch (Exception e) { - throw new RuntimeException(e); + // TODO handle failed update + throw new RuntimeException( + String.format("Could not pull new branch '%s' into central repository '%s'", + request.getNewBranch(), + getRepository().getNamespaceAndName()), + e); } return Branch.normalBranch(request.getNewBranch(), emptyChangeset.getNode()); From 2e93a9efecb37d7938f1680e043ccb94a246d7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 29 Mar 2019 15:39:44 +0100 Subject: [PATCH 26/31] Implement peer review comments --- .../repository/api/BranchCommandBuilder.java | 16 ++++- .../scm/repository/api/RepositoryService.java | 40 ++++++------ .../scm/repository/spi/BranchCommand.java | 4 +- .../IntegrateChangesFromWorkdirException.java | 23 +++++++ .../scm/repository/util/CloseableWrapper.java | 6 +- .../repository/util/SimpleWorkdirFactory.java | 2 +- .../scm/repository/spi/GitBranchCommand.java | 7 +-- .../scm/repository/spi/GitMergeCommand.java | 2 - scm-plugins/scm-hg-plugin/pom.xml | 6 -- .../scm/repository/spi/HgBranchCommand.java | 63 ++++++++++++------- .../main/java/sonia/scm/web/HgCGIServlet.java | 8 --- .../ContextualFallbackExceptionMapper.java | 32 ++++++++++ .../main/resources/locales/de/plugins.json | 4 ++ .../main/resources/locales/en/plugins.json | 4 ++ 14 files changed, 143 insertions(+), 74 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirException.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/ContextualFallbackExceptionMapper.java diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java index aa8485efca..4ec78c9bdc 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchCommandBuilder.java @@ -32,8 +32,6 @@ package sonia.scm.repository.api; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.spi.BranchCommand; @@ -48,12 +46,24 @@ public final class BranchCommandBuilder { this.command = command; } + /** + * Specifies the source branch, which the new branch should be based on. + * + * @param parentBranch The base branch for the new branch. + * @return This builder. + */ public BranchCommandBuilder from(String parentBranch) { request.setParentBranch(parentBranch); return this; } - public Branch branch(String name) throws IOException { + /** + * Execute the command and create a new branch with the given name. + * @param name The name of the new branch. + * @return The created branch. + * @throws IOException + */ + public Branch branch(String name) { request.setNewBranch(name); return command.branch(request); } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java index d59362a7f3..e11afa4be9 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java @@ -39,6 +39,7 @@ import sonia.scm.repository.Changeset; import sonia.scm.repository.Feature; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.RepositoryServiceProvider; import java.io.Closeable; @@ -82,10 +83,9 @@ import java.util.stream.Stream; * @apiviz.uses sonia.scm.repository.api.MergeCommandBuilder * @since 1.17 */ -@Slf4j public final class RepositoryService implements Closeable { - private static final Logger logger = LoggerFactory.getLogger(RepositoryService.class); + private static final Logger LOG = LoggerFactory.getLogger(RepositoryService.class); private final CacheManager cacheManager; private final PreProcessorUtil preProcessorUtil; @@ -131,7 +131,7 @@ public final class RepositoryService implements Closeable { try { provider.close(); } catch (IOException ex) { - log.error("Could not close repository service provider", ex); + LOG.error("Could not close repository service provider", ex); } } @@ -143,7 +143,7 @@ public final class RepositoryService implements Closeable { * by the implementation of the repository service provider. */ public BlameCommandBuilder getBlameCommand() { - logger.debug("create blame command for repository {}", + LOG.debug("create blame command for repository {}", repository.getNamespaceAndName()); return new BlameCommandBuilder(cacheManager, provider.getBlameCommand(), @@ -158,7 +158,7 @@ public final class RepositoryService implements Closeable { * by the implementation of the repository service provider. */ public BranchesCommandBuilder getBranchesCommand() { - logger.debug("create branches command for repository {}", + LOG.debug("create branches command for repository {}", repository.getNamespaceAndName()); return new BranchesCommandBuilder(cacheManager, @@ -173,7 +173,8 @@ public final class RepositoryService implements Closeable { * by the implementation of the repository service provider. */ public BranchCommandBuilder getBranchCommand() { - logger.debug("create branch command for repository {}", + RepositoryPermissions.push(getRepository()).check(); + LOG.debug("create branch command for repository {}", repository.getNamespaceAndName()); return new BranchCommandBuilder(provider.getBranchCommand()); @@ -187,7 +188,7 @@ public final class RepositoryService implements Closeable { * by the implementation of the repository service provider. */ public BrowseCommandBuilder getBrowseCommand() { - logger.debug("create browse command for repository {}", + LOG.debug("create browse command for repository {}", repository.getNamespaceAndName()); return new BrowseCommandBuilder(cacheManager, provider.getBrowseCommand(), @@ -203,7 +204,7 @@ public final class RepositoryService implements Closeable { * @since 1.43 */ public BundleCommandBuilder getBundleCommand() { - logger.debug("create bundle command for repository {}", + LOG.debug("create bundle command for repository {}", repository.getNamespaceAndName()); return new BundleCommandBuilder(provider.getBundleCommand(), repository); @@ -217,7 +218,7 @@ public final class RepositoryService implements Closeable { * by the implementation of the repository service provider. */ public CatCommandBuilder getCatCommand() { - logger.debug("create cat command for repository {}", + LOG.debug("create cat command for repository {}", repository.getNamespaceAndName()); return new CatCommandBuilder(provider.getCatCommand()); @@ -232,7 +233,7 @@ public final class RepositoryService implements Closeable { * by the implementation of the repository service provider. */ public DiffCommandBuilder getDiffCommand() { - logger.debug("create diff command for repository {}", + LOG.debug("create diff command for repository {}", repository.getNamespaceAndName()); return new DiffCommandBuilder(provider.getDiffCommand(), provider.getSupportedFeatures()); @@ -248,7 +249,7 @@ public final class RepositoryService implements Closeable { * @since 1.31 */ public IncomingCommandBuilder getIncomingCommand() { - logger.debug("create incoming command for repository {}", + LOG.debug("create incoming command for repository {}", repository.getNamespaceAndName()); return new IncomingCommandBuilder(cacheManager, @@ -263,7 +264,7 @@ public final class RepositoryService implements Closeable { * by the implementation of the repository service provider. */ public LogCommandBuilder getLogCommand() { - logger.debug("create log command for repository {}", + LOG.debug("create log command for repository {}", repository.getNamespaceAndName()); return new LogCommandBuilder(cacheManager, provider.getLogCommand(), @@ -278,7 +279,7 @@ public final class RepositoryService implements Closeable { * by the implementation of the repository service provider. */ public ModificationsCommandBuilder getModificationsCommand() { - logger.debug("create modifications command for repository {}", repository.getNamespaceAndName()); + LOG.debug("create modifications command for repository {}", repository.getNamespaceAndName()); return new ModificationsCommandBuilder(provider.getModificationsCommand(),repository, cacheManager.getCache(ModificationsCommandBuilder.CACHE_NAME), preProcessorUtil); } @@ -291,7 +292,7 @@ public final class RepositoryService implements Closeable { * @since 1.31 */ public OutgoingCommandBuilder getOutgoingCommand() { - logger.debug("create outgoing command for repository {}", + LOG.debug("create outgoing command for repository {}", repository.getNamespaceAndName()); return new OutgoingCommandBuilder(cacheManager, @@ -307,7 +308,7 @@ public final class RepositoryService implements Closeable { * @since 1.31 */ public PullCommandBuilder getPullCommand() { - logger.debug("create pull command for repository {}", + LOG.debug("create pull command for repository {}", repository.getNamespaceAndName()); return new PullCommandBuilder(provider.getPullCommand(), repository); @@ -322,7 +323,7 @@ public final class RepositoryService implements Closeable { * @since 1.31 */ public PushCommandBuilder getPushCommand() { - logger.debug("create push command for repository {}", + LOG.debug("create push command for repository {}", repository.getNamespaceAndName()); return new PushCommandBuilder(provider.getPushCommand()); @@ -345,7 +346,7 @@ public final class RepositoryService implements Closeable { * by the implementation of the repository service provider. */ public TagsCommandBuilder getTagsCommand() { - logger.debug("create tags command for repository {}", + LOG.debug("create tags command for repository {}", repository.getNamespaceAndName()); return new TagsCommandBuilder(cacheManager, provider.getTagsCommand(), @@ -361,7 +362,7 @@ public final class RepositoryService implements Closeable { * @since 1.43 */ public UnbundleCommandBuilder getUnbundleCommand() { - logger.debug("create unbundle command for repository {}", + LOG.debug("create unbundle command for repository {}", repository.getNamespaceAndName()); return new UnbundleCommandBuilder(provider.getUnbundleCommand(), @@ -378,7 +379,8 @@ public final class RepositoryService implements Closeable { * @since 2.0.0 */ public MergeCommandBuilder getMergeCommand() { - logger.debug("create merge command for repository {}", + RepositoryPermissions.push(getRepository()).check(); + LOG.debug("create merge command for repository {}", repository.getNamespaceAndName()); return new MergeCommandBuilder(provider.getMergeCommand()); diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java index 181e373382..c659c7fac0 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BranchCommand.java @@ -36,11 +36,9 @@ package sonia.scm.repository.spi; import sonia.scm.repository.Branch; import sonia.scm.repository.api.BranchRequest; -import java.io.IOException; - /** * @since 2.0 */ public interface BranchCommand { - Branch branch(BranchRequest name) throws IOException; + Branch branch(BranchRequest name); } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirException.java b/scm-core/src/main/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirException.java new file mode 100644 index 0000000000..c7c378500b --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/IntegrateChangesFromWorkdirException.java @@ -0,0 +1,23 @@ +package sonia.scm.repository.spi; + +import sonia.scm.ContextEntry; +import sonia.scm.ExceptionWithContext; +import sonia.scm.repository.Repository; + +public class IntegrateChangesFromWorkdirException extends ExceptionWithContext { + + private static final String CODE = "CHRM7IQzo1"; + + public IntegrateChangesFromWorkdirException(Repository repository, String message) { + super(ContextEntry.ContextBuilder.entity(repository).build(), message); + } + + public IntegrateChangesFromWorkdirException(Repository repository, String message, Exception cause) { + super(ContextEntry.ContextBuilder.entity(repository).build(), message, cause); + } + + @Override + public String getCode() { + return CODE; + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java b/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java index 1d353b2f35..a33af3ecb1 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/CloseableWrapper.java @@ -16,10 +16,6 @@ public class CloseableWrapper implements AutoCloseable @Override public void close() { - try { - cleanup.accept(wrapped); - } catch (Throwable t) { - throw new RuntimeException(t); - } + cleanup.accept(wrapped); } } diff --git a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java index 9642f72ae7..0872242612 100644 --- a/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/util/SimpleWorkdirFactory.java @@ -16,7 +16,7 @@ public abstract class SimpleWorkdirFactory implements WorkdirFactory private final File poolDirectory; public SimpleWorkdirFactory() { - this(new File(System.getProperty("java.io.tmpdir"), "scmm-work-pool")); + this(new File(System.getProperty("scm.workdir" , System.getProperty("java.io.tmpdir")), "scm-work")); } public SimpleWorkdirFactory(File poolDirectory) { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java index 208b534343..a0ea403feb 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java @@ -79,11 +79,8 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman private void handlePushError(RemoteRefUpdate remoteRefUpdate, BranchRequest request, Repository repository) { if (remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) { // TODO handle failed remote update - throw new RuntimeException( - String.format("Could not pull new branch '%s' into central repository '%s': %s", - request.getNewBranch(), - repository.getNamespaceAndName(), - remoteRefUpdate.getMessage())); + throw new IntegrateChangesFromWorkdirException(repository, + String.format("Could not push new branch '%s' into central repository", request.getNewBranch())); } } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 6f4d09e4f6..94fe84446b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -47,8 +47,6 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand @Override public MergeCommandResult merge(MergeCommandRequest request) { - RepositoryPermissions.push(context.getRepository().getId()).check(); - try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { Repository repository = workingCopy.getWorkingRepository(); logger.debug("cloned repository to folder {}", repository.getWorkTree()); diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 3c49becb56..025f79add3 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -28,12 +28,6 @@ - - io.jsonwebtoken - jjwt - 0.4 - - diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java index 5a0b6d0cd1..750bfc1332 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchCommand.java @@ -34,12 +34,15 @@ import com.aragost.javahg.Changeset; import com.aragost.javahg.commands.CommitCommand; import com.aragost.javahg.commands.PullCommand; import com.aragost.javahg.commands.UpdateCommand; +import org.apache.shiro.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.Branch; +import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; import sonia.scm.repository.api.BranchRequest; import sonia.scm.repository.util.WorkingCopy; +import sonia.scm.user.User; import java.io.IOException; @@ -59,37 +62,53 @@ public class HgBranchCommand extends AbstractCommand implements BranchCommand { } @Override - public Branch branch(BranchRequest request) throws IOException { + public Branch branch(BranchRequest request) { try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(getContext())) { com.aragost.javahg.Repository repository = workingCopy.getWorkingRepository(); - if (request.getParentBranch() != null) { - UpdateCommand.on(repository).rev(request.getParentBranch()).execute(); - } - com.aragost.javahg.commands.BranchCommand.on(repository).set(request.getNewBranch()); - Changeset emptyChangeset = CommitCommand - .on(repository) - .user("SCM-Manager") - .message("Create new branch " + request.getNewBranch()) - .execute(); + checkoutParentBranchIfSpecified(request, repository); + + Changeset emptyChangeset = createNewBranchWithEmptyCommit(request, repository); LOG.debug("Created new branch '{}' in repository {} with changeset {}", request.getNewBranch(), getRepository().getNamespaceAndName(), emptyChangeset.getNode()); - try { - com.aragost.javahg.commands.PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository()); - workdirFactory.configure(pullCommand); - pullCommand.execute(workingCopy.getDirectory().getAbsolutePath()); - } catch (Exception e) { - // TODO handle failed update - throw new RuntimeException( - String.format("Could not pull new branch '%s' into central repository '%s'", - request.getNewBranch(), - getRepository().getNamespaceAndName()), - e); - } + pullNewBranchIntoCentralRepository(request, workingCopy); return Branch.normalBranch(request.getNewBranch(), emptyChangeset.getNode()); } } + + private void checkoutParentBranchIfSpecified(BranchRequest request, com.aragost.javahg.Repository repository) { + if (request.getParentBranch() != null) { + try { + UpdateCommand.on(repository).rev(request.getParentBranch()).execute(); + } catch (IOException e) { + throw new InternalRepositoryException(getRepository(), "Could not check out parent branch " + request.getParentBranch(), e); + } + } + } + + private Changeset createNewBranchWithEmptyCommit(BranchRequest request, com.aragost.javahg.Repository repository) { + com.aragost.javahg.commands.BranchCommand.on(repository).set(request.getNewBranch()); + User currentUser = SecurityUtils.getSubject().getPrincipals().oneByType(User.class); + return CommitCommand + .on(repository) + .user(String.format("%s <%s>", currentUser.getDisplayName(), currentUser.getMail())) + .message("Create new branch " + request.getNewBranch()) + .execute(); + } + + private void pullNewBranchIntoCentralRepository(BranchRequest request, WorkingCopy workingCopy) { + try { + PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository()); + workdirFactory.configure(pullCommand); + pullCommand.execute(workingCopy.getDirectory().getAbsolutePath()); + } catch (Exception e) { + // TODO handle failed update + throw new IntegrateChangesFromWorkdirException(getRepository(), + String.format("Could not pull new branch '%s' into central repository", request.getNewBranch()), + e); + } + } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java index 803db0f129..2961f4ecb1 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java @@ -203,14 +203,6 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet executor.setContentLengthWorkaround(true); hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap()); - // unused ??? - HttpSession session = request.getSession(false); - - if (session != null) - { - passSessionAttributes(executor.getEnvironment(), session); - } - String interpreter = getInterpreter(); if (interpreter != null) diff --git a/scm-webapp/src/main/java/sonia/scm/api/ContextualFallbackExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/ContextualFallbackExceptionMapper.java new file mode 100644 index 0000000000..6034ff3df5 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/ContextualFallbackExceptionMapper.java @@ -0,0 +1,32 @@ +package sonia.scm.api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import sonia.scm.ExceptionWithContext; +import sonia.scm.api.v2.resources.ErrorDto; +import sonia.scm.web.VndMediaType; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class ContextualFallbackExceptionMapper implements ExceptionMapper { + + private static final Logger logger = LoggerFactory.getLogger(ContextualFallbackExceptionMapper.class); + + @Override + public Response toResponse(ExceptionWithContext exception) { + logger.warn("mapping unexpected {} to status code 500", exception.getClass().getName(), exception); + ErrorDto errorDto = new ErrorDto(); + errorDto.setMessage(exception.getMessage()); + errorDto.setContext(exception.getContext()); + errorDto.setErrorCode(exception.getCode()); + errorDto.setTransactionId(MDC.get("transaction_id")); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(errorDto) + .type(VndMediaType.ERROR_TYPE) + .build(); + } +} diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json index a4f4589245..41bb53de1e 100644 --- a/scm-webapp/src/main/resources/locales/de/plugins.json +++ b/scm-webapp/src/main/resources/locales/de/plugins.json @@ -147,6 +147,10 @@ "3zR9vPNIE1": { "displayName": "Ungültige Eingabe", "description": "Die eingegebenen Daten konnten nicht validiert werden. Bitte korrigieren Sie die Eingaben und senden Sie sie erneut." + }, + "CHRM7IQzo1": { + "displayName": "Änderung fehlgeschlagen", + "description": "Die Änderung ist fehlgeschlagen. Bitte wenden Sie sich an ihren Administrator für weitere Hinweise." } }, "namespaceStrategies": { diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json index 050312b693..0ad62ddf5c 100644 --- a/scm-webapp/src/main/resources/locales/en/plugins.json +++ b/scm-webapp/src/main/resources/locales/en/plugins.json @@ -147,6 +147,10 @@ "3zR9vPNIE1": { "displayName": "Illegal input", "description": "The values could not be validated. Please correct your input and try again." + }, + "CHRM7IQzo1": { + "displayName": "Change failed", + "description": "The change failed. Please contact your administrator for further assistance." } }, "namespaceStrategies": { From f8a59a4d0f1066f781e365ec7a02ea62850eb978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 29 Mar 2019 16:13:29 +0100 Subject: [PATCH 27/31] Use new IntegrateChangesFromWorkdirException in merge command --- .../main/java/sonia/scm/repository/spi/GitMergeCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 94fe84446b..58c71b8dac 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -185,7 +185,8 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand try { clone.push().call(); } catch (GitAPIException e) { - throw new InternalRepositoryException(context.getRepository(), "could not push merged branch " + target + " to origin", e); + throw new IntegrateChangesFromWorkdirException(repository, + "could not push merged branch " + target + " into central repository", e); } logger.debug("pushed merged branch {}", target); } From ae79a5ad9d2554529b5dca672eeb535950a34d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 1 Apr 2019 13:51:34 +0200 Subject: [PATCH 28/31] Introduce own manager for reduced model object Move autocomplete functionality to own managers and add functions to load reduced model objects by id --- .../main/java/sonia/scm/DisplayManager.java | 19 +++++++++ scm-core/src/main/java/sonia/scm/Manager.java | 2 - .../main/java/sonia/scm/TransformFilter.java | 6 ++- .../java/sonia/scm/group/DisplayGroup.java | 26 ++++++++++++ .../sonia/scm/group/GroupDisplayManager.java | 6 +++ .../java/sonia/scm/group/GroupManager.java | 10 ----- .../scm/group/GroupManagerDecorator.java | 5 --- .../java/sonia/scm/search/SearchUtil.java | 8 ++-- .../main/java/sonia/scm/user/DisplayUser.java | 32 ++++++++++++++ .../sonia/scm/user/UserDisplayManager.java | 6 +++ .../main/java/sonia/scm/user/UserManager.java | 8 ---- .../sonia/scm/user/UserManagerDecorator.java | 5 --- .../java/sonia/scm/GenericDisplayManager.java | 42 +++++++++++++++++++ .../main/java/sonia/scm/ScmServletModule.java | 7 ++++ .../v2/resources/AutoCompleteResource.java | 18 ++++---- .../scm/api/v2/resources/UserResource.java | 1 + .../scm/group/DefaultGroupDisplayManager.java | 25 +++++++++++ .../sonia/scm/group/DefaultGroupManager.java | 9 +--- .../scm/user/DefaultUserDisplayManager.java | 25 +++++++++++ .../sonia/scm/user/DefaultUserManager.java | 38 +---------------- .../resources/AutoCompleteResourceTest.java | 14 +++---- 21 files changed, 214 insertions(+), 98 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/DisplayManager.java create mode 100644 scm-core/src/main/java/sonia/scm/group/DisplayGroup.java create mode 100644 scm-core/src/main/java/sonia/scm/group/GroupDisplayManager.java create mode 100644 scm-core/src/main/java/sonia/scm/user/DisplayUser.java create mode 100644 scm-core/src/main/java/sonia/scm/user/UserDisplayManager.java create mode 100644 scm-webapp/src/main/java/sonia/scm/GenericDisplayManager.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/DefaultGroupDisplayManager.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/DefaultUserDisplayManager.java diff --git a/scm-core/src/main/java/sonia/scm/DisplayManager.java b/scm-core/src/main/java/sonia/scm/DisplayManager.java new file mode 100644 index 0000000000..72113a483d --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/DisplayManager.java @@ -0,0 +1,19 @@ +package sonia.scm; + +import java.util.Collection; +import java.util.Optional; + +public interface DisplayManager { + + int DEFAULT_LIMIT = 5; + + /** + * Returns a {@link Collection} of filtered objects + * + * @param filter the searched string + * @return filtered object from the store + */ + Collection autocomplete(String filter); + + Optional get(String id); +} diff --git a/scm-core/src/main/java/sonia/scm/Manager.java b/scm-core/src/main/java/sonia/scm/Manager.java index 390777958d..9a7f21d3ef 100644 --- a/scm-core/src/main/java/sonia/scm/Manager.java +++ b/scm-core/src/main/java/sonia/scm/Manager.java @@ -47,8 +47,6 @@ public interface Manager extends HandlerBase, LastModifiedAware { - int DEFAULT_LIMIT = 5; - /** * Reloads a object from store and overwrites all changes. diff --git a/scm-core/src/main/java/sonia/scm/TransformFilter.java b/scm-core/src/main/java/sonia/scm/TransformFilter.java index e872ab384f..fa798fe36e 100644 --- a/scm-core/src/main/java/sonia/scm/TransformFilter.java +++ b/scm-core/src/main/java/sonia/scm/TransformFilter.java @@ -39,8 +39,10 @@ package sonia.scm; * @author Sebastian Sdorra * * @param type of objects to transform + * @param result type of the transformation */ -public interface TransformFilter +@FunctionalInterface +public interface TransformFilter { /** @@ -52,5 +54,5 @@ public interface TransformFilter * * @return tranformed object */ - public T accept(T item); + R accept(T item); } diff --git a/scm-core/src/main/java/sonia/scm/group/DisplayGroup.java b/scm-core/src/main/java/sonia/scm/group/DisplayGroup.java new file mode 100644 index 0000000000..a16a3046e9 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/group/DisplayGroup.java @@ -0,0 +1,26 @@ +package sonia.scm.group; + +import sonia.scm.ReducedModelObject; + +public class DisplayGroup implements ReducedModelObject { + + private final String id; + private final String displayName; + + public static DisplayGroup from(Group group) { + return new DisplayGroup(group.getId(), group.getDescription()); + } + + private DisplayGroup(String id, String displayName) { + this.id = id; + this.displayName = displayName; + } + + public String getId() { + return id; + } + + public String getDisplayName() { + return displayName; + } +} diff --git a/scm-core/src/main/java/sonia/scm/group/GroupDisplayManager.java b/scm-core/src/main/java/sonia/scm/group/GroupDisplayManager.java new file mode 100644 index 0000000000..d9188e9402 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/group/GroupDisplayManager.java @@ -0,0 +1,6 @@ +package sonia.scm.group; + +import sonia.scm.DisplayManager; + +public interface GroupDisplayManager extends DisplayManager { +} diff --git a/scm-core/src/main/java/sonia/scm/group/GroupManager.java b/scm-core/src/main/java/sonia/scm/group/GroupManager.java index 08057ae3db..288196894d 100644 --- a/scm-core/src/main/java/sonia/scm/group/GroupManager.java +++ b/scm-core/src/main/java/sonia/scm/group/GroupManager.java @@ -61,14 +61,4 @@ public interface GroupManager * @return all groups assigned to the given member */ public Collection getGroupsForMember(String member); - - - /** - * Returns a {@link java.util.Collection} of filtered objects - * - * @param filter the searched string - * @return filtered object from the store - */ - Collection autocomplete(String filter); - } diff --git a/scm-core/src/main/java/sonia/scm/group/GroupManagerDecorator.java b/scm-core/src/main/java/sonia/scm/group/GroupManagerDecorator.java index ef6de4164c..e2367d863c 100644 --- a/scm-core/src/main/java/sonia/scm/group/GroupManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/group/GroupManagerDecorator.java @@ -109,11 +109,6 @@ public class GroupManagerDecorator return decorated.getGroupsForMember(member); } - @Override - public Collection autocomplete(String filter) { - return decorated.autocomplete(filter); - } - //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-core/src/main/java/sonia/scm/search/SearchUtil.java b/scm-core/src/main/java/sonia/scm/search/SearchUtil.java index 90c65e3456..281e00a517 100644 --- a/scm-core/src/main/java/sonia/scm/search/SearchUtil.java +++ b/scm-core/src/main/java/sonia/scm/search/SearchUtil.java @@ -159,17 +159,17 @@ public final class SearchUtil * * @return */ - public static Collection search(SearchRequest searchRequest, - Collection collection, TransformFilter filter) + public static Collection search(SearchRequest searchRequest, + Collection collection, TransformFilter filter) { - List items = new ArrayList(); + List items = new ArrayList<>(); int index = 0; int counter = 0; Iterator it = collection.iterator(); while (it.hasNext()) { - T item = filter.accept(it.next()); + R item = filter.accept(it.next()); if (item != null) { diff --git a/scm-core/src/main/java/sonia/scm/user/DisplayUser.java b/scm-core/src/main/java/sonia/scm/user/DisplayUser.java new file mode 100644 index 0000000000..7a11dfbab4 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/user/DisplayUser.java @@ -0,0 +1,32 @@ +package sonia.scm.user; + +import sonia.scm.ReducedModelObject; + +public class DisplayUser implements ReducedModelObject { + + private final String id; + private final String displayName; + private final String mail; + + public static DisplayUser from(User user) { + return new DisplayUser(user.getId(), user.getDisplayName(), user.getMail()); + } + + private DisplayUser(String id, String displayName, String mail) { + this.id = id; + this.displayName = displayName; + this.mail = mail; + } + + public String getId() { + return id; + } + + public String getDisplayName() { + return displayName; + } + + public String getMail() { + return mail; + } +} diff --git a/scm-core/src/main/java/sonia/scm/user/UserDisplayManager.java b/scm-core/src/main/java/sonia/scm/user/UserDisplayManager.java new file mode 100644 index 0000000000..159025f5ec --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/user/UserDisplayManager.java @@ -0,0 +1,6 @@ +package sonia.scm.user; + +import sonia.scm.DisplayManager; + +public interface UserDisplayManager extends DisplayManager { +} diff --git a/scm-core/src/main/java/sonia/scm/user/UserManager.java b/scm-core/src/main/java/sonia/scm/user/UserManager.java index f301c1f2b1..a735918c59 100644 --- a/scm-core/src/main/java/sonia/scm/user/UserManager.java +++ b/scm-core/src/main/java/sonia/scm/user/UserManager.java @@ -75,14 +75,6 @@ public interface UserManager return getDefaultType().equals(user.getType()); } - /** - * Returns a {@link java.util.Collection} of filtered objects - * - * @param filter the searched string - * @return filtered object from the store - */ - Collection autocomplete(String filter); - /** * Changes the password of the logged in user. * @param oldPassword The current encrypted password of the user. diff --git a/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java b/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java index 0384fe1b52..0b88d856ff 100644 --- a/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java @@ -121,11 +121,6 @@ public class UserManagerDecorator extends ManagerDecorator return decorated.getDefaultType(); } - @Override - public Collection autocomplete(String filter) { - return decorated.autocomplete(filter); - } - @Override public void changePasswordForLoggedInUser(String oldPassword, String newPassword) { decorated.changePasswordForLoggedInUser(oldPassword, newPassword); diff --git a/scm-webapp/src/main/java/sonia/scm/GenericDisplayManager.java b/scm-webapp/src/main/java/sonia/scm/GenericDisplayManager.java new file mode 100644 index 0000000000..5ad4f6343a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/GenericDisplayManager.java @@ -0,0 +1,42 @@ +package sonia.scm; + +import sonia.scm.search.SearchRequest; +import sonia.scm.search.SearchUtil; + +import java.util.Collection; +import java.util.Optional; +import java.util.function.Function; + +import static java.util.Optional.ofNullable; +import static sonia.scm.group.DisplayGroup.from; + +public abstract class GenericDisplayManager implements DisplayManager { + + private final GenericDAO dao; + private final Function transform; + + protected GenericDisplayManager(GenericDAO dao, Function transform) { + this.dao = dao; + this.transform = transform; + } + + @Override + public Collection autocomplete(String filter) { + checkPermission(); + SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT); + return SearchUtil.search( + searchRequest, + dao.getAll(), + object -> matches(searchRequest, object)? transform.apply(object): null + ); + } + + protected abstract void checkPermission(); + + protected abstract boolean matches(SearchRequest searchRequest, D object); + + @Override + public Optional get(String id) { + return ofNullable(dao.get(id)).map(transform); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 7c7dec47ff..a3661a25f0 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -46,8 +46,10 @@ import sonia.scm.cache.CacheManager; import sonia.scm.cache.GuavaCacheManager; import sonia.scm.config.ScmConfiguration; import sonia.scm.event.ScmEventBus; +import sonia.scm.group.DefaultGroupDisplayManager; import sonia.scm.group.DefaultGroupManager; import sonia.scm.group.GroupDAO; +import sonia.scm.group.GroupDisplayManager; import sonia.scm.group.GroupManager; import sonia.scm.group.GroupManagerProvider; import sonia.scm.group.xml.XmlGroupDAO; @@ -102,8 +104,10 @@ import sonia.scm.template.MustacheTemplateEngine; import sonia.scm.template.TemplateEngine; import sonia.scm.template.TemplateEngineFactory; import sonia.scm.template.TemplateServlet; +import sonia.scm.user.DefaultUserDisplayManager; import sonia.scm.user.DefaultUserManager; import sonia.scm.user.UserDAO; +import sonia.scm.user.UserDisplayManager; import sonia.scm.user.UserManager; import sonia.scm.user.UserManagerProvider; import sonia.scm.user.xml.XmlUserDAO; @@ -268,8 +272,11 @@ public class ScmServletModule extends ServletModule RepositoryManagerProvider.class); bindDecorated(UserManager.class, DefaultUserManager.class, UserManagerProvider.class); + bind(UserDisplayManager.class, DefaultUserDisplayManager.class); bindDecorated(GroupManager.class, DefaultGroupManager.class, GroupManagerProvider.class); + bind(GroupDisplayManager.class, DefaultGroupDisplayManager.class); + bind(CGIExecutorFactory.class, DefaultCGIExecutorFactory.class); // bind sslcontext provider diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java index 005e29e9ea..38b421f573 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java @@ -4,8 +4,8 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import org.hibernate.validator.constraints.NotEmpty; import sonia.scm.ReducedModelObject; -import sonia.scm.group.GroupManager; -import sonia.scm.user.UserManager; +import sonia.scm.group.GroupDisplayManager; +import sonia.scm.user.UserDisplayManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; @@ -30,14 +30,14 @@ public class AutoCompleteResource { private ReducedObjectModelToDtoMapper mapper; - private UserManager userManager; - private GroupManager groupManager; + private UserDisplayManager userDisplayManager; + private GroupDisplayManager groupDisplayManager; @Inject - public AutoCompleteResource(ReducedObjectModelToDtoMapper mapper, UserManager userManager, GroupManager groupManager) { + public AutoCompleteResource(ReducedObjectModelToDtoMapper mapper, UserDisplayManager userDisplayManager, GroupDisplayManager groupDisplayManager) { this.mapper = mapper; - this.userManager = userManager; - this.groupManager = groupManager; + this.userDisplayManager = userDisplayManager; + this.groupDisplayManager = groupDisplayManager; } @GET @@ -51,7 +51,7 @@ public class AutoCompleteResource { @ResponseCode(code = 500, condition = "internal server error") }) public List searchUser(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) { - return map(userManager.autocomplete(filter)); + return map(userDisplayManager.autocomplete(filter)); } @GET @@ -65,7 +65,7 @@ public class AutoCompleteResource { @ResponseCode(code = 500, condition = "internal server error") }) public List searchGroup(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) { - return map(groupManager.autocomplete(filter)); + return map(groupDisplayManager.autocomplete(filter)); } private List map(Collection autocomplete) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java index 6e90b4e6ec..e2a6cc797e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java @@ -6,6 +6,7 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.authc.credential.PasswordService; import sonia.scm.user.User; import sonia.scm.user.UserManager; +import sonia.scm.user.UserPermissions; import sonia.scm.web.VndMediaType; import javax.inject.Inject; diff --git a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupDisplayManager.java b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupDisplayManager.java new file mode 100644 index 0000000000..abffa46e41 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupDisplayManager.java @@ -0,0 +1,25 @@ +package sonia.scm.group; + +import sonia.scm.GenericDisplayManager; +import sonia.scm.search.SearchRequest; +import sonia.scm.search.SearchUtil; + +import javax.inject.Inject; + +public class DefaultGroupDisplayManager extends GenericDisplayManager implements GroupDisplayManager { + + @Inject + public DefaultGroupDisplayManager(GroupDAO groupDAO) { + super(groupDAO, DisplayGroup::from); + } + + @Override + protected void checkPermission() { + GroupPermissions.autocomplete().check(); + } + + @Override + protected boolean matches(SearchRequest searchRequest, Group group) { + return SearchUtil.matchesOne(searchRequest, group.getName(), group.getDescription()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java index 6bf850f99d..3320773e57 100644 --- a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java +++ b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java @@ -195,7 +195,7 @@ public class DefaultGroupManager extends AbstractGroupManager final PermissionActionCheck check = GroupPermissions.read(); return SearchUtil.search(searchRequest, groupDAO.getAll(), - new TransformFilter() + new TransformFilter() { @Override public Group accept(Group group) @@ -241,13 +241,6 @@ public class DefaultGroupManager extends AbstractGroupManager return group; } - @Override - public Collection autocomplete(String filter) { - GroupPermissions.autocomplete().check(); - SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT); - return SearchUtil.search(searchRequest, groupDAO.getAll(), group -> matches(searchRequest,group)?group:null); - } - /** * Method description * diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserDisplayManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserDisplayManager.java new file mode 100644 index 0000000000..d27b360922 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserDisplayManager.java @@ -0,0 +1,25 @@ +package sonia.scm.user; + +import sonia.scm.GenericDisplayManager; +import sonia.scm.search.SearchRequest; +import sonia.scm.search.SearchUtil; + +import javax.inject.Inject; + +public class DefaultUserDisplayManager extends GenericDisplayManager implements UserDisplayManager { + + @Inject + public DefaultUserDisplayManager(UserDAO userDAO) { + super(userDAO, DisplayUser::from); + } + + @Override + protected void checkPermission() { + UserPermissions.autocomplete().check(); + } + + @Override + protected boolean matches(SearchRequest searchRequest, User user) { + return SearchUtil.matchesOne(searchRequest, user.getName(), user.getDisplayName(), user.getMail()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 3b78267631..a6c40b24f4 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -212,13 +212,6 @@ public class DefaultUserManager extends AbstractUserManager fresh.copyProperties(user); } - @Override - public Collection autocomplete(String filter) { - UserPermissions.autocomplete().check(); - SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT); - return SearchUtil.search(searchRequest, userDAO.getAll(), user -> matches(searchRequest,user)?user:null); - } - /** * Method description * @@ -236,7 +229,7 @@ public class DefaultUserManager extends AbstractUserManager } final PermissionActionCheck check = UserPermissions.read(); - return SearchUtil.search(searchRequest, userDAO.getAll(), new TransformFilter() { + return SearchUtil.search(searchRequest, userDAO.getAll(), new TransformFilter() { @Override public User accept(User user) { @@ -415,35 +408,6 @@ public class DefaultUserManager extends AbstractUserManager this.modify(user); } - /** - * Method description - * - * - * @param unmarshaller - * @param path - */ - private void createDefaultAccount(Unmarshaller unmarshaller, String path) - { - InputStream input = DefaultUserManager.class.getResourceAsStream(path); - - try - { - User user = (User) unmarshaller.unmarshal(input); - - user.setType(userDAO.getType()); - user.setCreationDate(System.currentTimeMillis()); - userDAO.add(user); - } - catch (Exception ex) - { - logger.error("could not create account", ex); - } - finally - { - IOUtil.close(input); - } - } - //~--- fields --------------------------------------------------------------- private final UserDAO userDAO; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java index d392aefe4e..ac89d02bfb 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java @@ -14,16 +14,14 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.Manager; -import sonia.scm.group.DefaultGroupManager; +import sonia.scm.DisplayManager; +import sonia.scm.group.DefaultGroupDisplayManager; import sonia.scm.group.Group; -import sonia.scm.group.GroupManager; import sonia.scm.group.xml.XmlGroupDAO; import sonia.scm.store.ConfigurationStore; import sonia.scm.store.ConfigurationStoreFactory; -import sonia.scm.user.DefaultUserManager; +import sonia.scm.user.DefaultUserDisplayManager; import sonia.scm.user.User; -import sonia.scm.user.UserManager; import sonia.scm.user.xml.XmlUserDAO; import sonia.scm.web.VndMediaType; import sonia.scm.xml.XmlDatabase; @@ -51,7 +49,7 @@ public class AutoCompleteResourceTest { public final ShiroRule shiroRule = new ShiroRule(); public static final String URL = "/" + AutoCompleteResource.PATH; - private final Integer defaultLimit = Manager.DEFAULT_LIMIT; + private final Integer defaultLimit = DisplayManager.DEFAULT_LIMIT; private Dispatcher dispatcher; private XmlUserDAO userDao; @@ -73,8 +71,8 @@ public class AutoCompleteResourceTest { XmlGroupDAO groupDAO = new XmlGroupDAO(storeFactory); groupDao = spy(groupDAO); ReducedObjectModelToDtoMapperImpl mapper = new ReducedObjectModelToDtoMapperImpl(); - UserManager userManager = new DefaultUserManager(this.userDao); - GroupManager groupManager = new DefaultGroupManager(groupDao); + DefaultUserDisplayManager userManager = new DefaultUserDisplayManager(this.userDao); + DefaultGroupDisplayManager groupManager = new DefaultGroupDisplayManager(groupDao); AutoCompleteResource autoCompleteResource = new AutoCompleteResource(mapper, userManager, groupManager); dispatcher = createDispatcher(autoCompleteResource); } From 009713161fbb19fcb3ebcd2f34c84702792301a5 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 1 Apr 2019 12:51:28 +0000 Subject: [PATCH 29/31] Close branch feature/create_branch From a121d0141ebc5399529fbce39d70eb97254cd8d7 Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Mon, 1 Apr 2019 13:43:02 +0000 Subject: [PATCH 30/31] Close branch feature/autocompete From f9be1cd92f619ff3597f04866a4ee5ec340f8901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 2 Apr 2019 10:30:19 +0200 Subject: [PATCH 31/31] Flip columns for base url config --- .../config/components/form/BaseUrlSettings.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scm-ui/src/config/components/form/BaseUrlSettings.js b/scm-ui/src/config/components/form/BaseUrlSettings.js index 3fa0e67b84..c5414d6a60 100644 --- a/scm-ui/src/config/components/form/BaseUrlSettings.js +++ b/scm-ui/src/config/components/form/BaseUrlSettings.js @@ -19,15 +19,6 @@ class BaseUrlSettings extends React.Component {
-
- -
{ helpText={t("help.baseUrlHelpText")} />
+
+ +
);