diff --git a/scm-core/src/main/java/sonia/scm/NoChangesMadeException.java b/scm-core/src/main/java/sonia/scm/NoChangesMadeException.java new file mode 100644 index 0000000000..9bf0363398 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/NoChangesMadeException.java @@ -0,0 +1,18 @@ +package sonia.scm; + +import sonia.scm.repository.Repository; + +public class NoChangesMadeException extends BadRequestException { + public NoChangesMadeException(Repository repository, String branch) { + super(ContextEntry.ContextBuilder.entity(repository).build(), "no changes detected to branch " + branch); + } + + public NoChangesMadeException(Repository repository) { + super(ContextEntry.ContextBuilder.entity(repository).build(), "no changes detected"); + } + + @Override + public String getCode() { + return "40RaYIeeR1"; + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java index 8fcfc937e5..0a2267e888 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/MergeCommandBuilder.java @@ -4,6 +4,7 @@ import com.google.common.base.Preconditions; import sonia.scm.repository.Person; import sonia.scm.repository.spi.MergeCommand; import sonia.scm.repository.spi.MergeCommandRequest; +import sonia.scm.repository.util.AuthorUtil; /** * Use this {@link MergeCommandBuilder} to merge two branches of a repository ({@link #executeMerge()}) or to check if @@ -126,6 +127,7 @@ public class MergeCommandBuilder { * @return The result of the merge. */ public MergeCommandResult executeMerge() { + AuthorUtil.setAuthorIfNotAvailable(request); Preconditions.checkArgument(request.isValid(), "revision to merge and target revision is required"); return mergeCommand.merge(request); } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java index 1eed0697cb..05c1babe03 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java @@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.repository.Person; import sonia.scm.repository.spi.ModifyCommand; import sonia.scm.repository.spi.ModifyCommandRequest; +import sonia.scm.repository.util.AuthorUtil; import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.util.IOUtil; @@ -94,22 +95,12 @@ public class ModifyCommandBuilder { return this; } - /** - * Move an existing file. - * @param sourcePath The path and the name of the file that should be moved. - * @param targetPath The new path and name the file should be moved to. - * @return This builder instance. - */ - public ModifyCommandBuilder moveFile(String sourcePath, String targetPath) { - request.addRequest(new ModifyCommandRequest.MoveFileRequest(sourcePath, targetPath)); - return this; - } - /** * Apply the changes and create a new commit with the given message and author. * @return The revision of the new commit. */ public String execute() { + AuthorUtil.setAuthorIfNotAvailable(request); try { Preconditions.checkArgument(request.isValid(), "commit message, branch and at least one request are required"); return command.execute(request); diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java index 45dfc0b2b7..223cf8c49e 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java @@ -5,10 +5,11 @@ import com.google.common.base.Objects; import com.google.common.base.Strings; import sonia.scm.Validateable; import sonia.scm.repository.Person; +import sonia.scm.repository.util.AuthorUtil.CommandWithAuthor; import java.io.Serializable; -public class MergeCommandRequest implements Validateable, Resetable, Serializable, Cloneable { +public class MergeCommandRequest implements Validateable, Resetable, Serializable, Cloneable, CommandWithAuthor { private static final long serialVersionUID = -2650236557922431528L; diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommand.java index b3ba882040..1bc64e0e08 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommand.java @@ -13,7 +13,5 @@ public interface ModifyCommand { void create(String toBeCreated, File file, boolean overwrite) throws IOException; void modify(String path, File file) throws IOException; - - void move(String sourcePath, String targetPath); } } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java index d61e17c785..43814f8729 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java @@ -5,6 +5,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.Validateable; import sonia.scm.repository.Person; +import sonia.scm.repository.util.AuthorUtil.CommandWithAuthor; import sonia.scm.util.IOUtil; import java.io.File; @@ -13,7 +14,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -public class ModifyCommandRequest implements Resetable, Validateable { +public class ModifyCommandRequest implements Resetable, Validateable, CommandWithAuthor { private static final Logger LOG = LoggerFactory.getLogger(ModifyCommandRequest.class); @@ -94,21 +95,6 @@ public class ModifyCommandRequest implements Resetable, Validateable { } } - public static class MoveFileRequest implements PartialRequest { - private final String sourcePath; - private final String targetPath; - - public MoveFileRequest(String sourcePath, String targetPath) { - this.sourcePath = sourcePath; - this.targetPath = targetPath; - } - - @Override - public void execute(ModifyCommand.Worker worker) { - worker.move(sourcePath, targetPath); - } - } - private abstract static class ContentModificationRequest implements PartialRequest { private final File content; diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyWorkerHelper.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyWorkerHelper.java new file mode 100644 index 0000000000..a1ff6b0eb3 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyWorkerHelper.java @@ -0,0 +1,88 @@ +package sonia.scm.repository.spi; + +import org.apache.commons.lang.StringUtils; +import sonia.scm.ContextEntry; +import sonia.scm.repository.Repository; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static sonia.scm.AlreadyExistsException.alreadyExists; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + +/** + * This "interface" is not really intended to be used as an interface but rather as + * a base class to reduce code redundancy in Worker instances. + */ +public interface ModifyWorkerHelper extends ModifyCommand.Worker { + + @Override + default void delete(String toBeDeleted) throws IOException { + Path fileToBeDeleted = new File(getWorkDir(), toBeDeleted).toPath(); + try { + Files.delete(fileToBeDeleted); + } catch (NoSuchFileException e) { + throw notFound(createFileContext(toBeDeleted)); + } + doScmDelete(toBeDeleted); + } + + void doScmDelete(String toBeDeleted); + + @Override + default void create(String toBeCreated, File file, boolean overwrite) throws IOException { + Path targetFile = new File(getWorkDir(), toBeCreated).toPath(); + createDirectories(targetFile); + if (overwrite) { + Files.move(file.toPath(), targetFile, REPLACE_EXISTING); + } else { + try { + Files.move(file.toPath(), targetFile); + } catch (FileAlreadyExistsException e) { + throw alreadyExists(createFileContext(toBeCreated)); + } + } + addFileToScm(toBeCreated, targetFile); + } + + default void modify(String path, File file) throws IOException { + Path targetFile = new File(getWorkDir(), path).toPath(); + createDirectories(targetFile); + if (!targetFile.toFile().exists()) { + throw notFound(createFileContext(path)); + } + Files.move(file.toPath(), targetFile, REPLACE_EXISTING); + addFileToScm(path, targetFile); + } + + void addFileToScm(String name, Path file); + + default ContextEntry.ContextBuilder createFileContext(String path) { + ContextEntry.ContextBuilder contextBuilder = entity("file", path); + if (!StringUtils.isEmpty(getBranch())) { + contextBuilder.in("branch", getBranch()); + } + contextBuilder.in(getRepository()); + return contextBuilder; + } + + default void createDirectories(Path targetFile) throws IOException { + try { + Files.createDirectories(targetFile.getParent()); + } catch (FileAlreadyExistsException e) { + throw alreadyExists(createFileContext(targetFile.toString())); + } + } + + File getWorkDir(); + + Repository getRepository(); + + String getBranch(); +} diff --git a/scm-core/src/main/java/sonia/scm/repository/util/AuthorUtil.java b/scm-core/src/main/java/sonia/scm/repository/util/AuthorUtil.java new file mode 100644 index 0000000000..5735889c3c --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/util/AuthorUtil.java @@ -0,0 +1,29 @@ +package sonia.scm.repository.util; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; +import sonia.scm.repository.Person; +import sonia.scm.user.User; + +public class AuthorUtil { + + public static void setAuthorIfNotAvailable(CommandWithAuthor request) { + if (request.getAuthor() == null) { + request.setAuthor(createAuthorFromSubject()); + } + } + + private static Person createAuthorFromSubject() { + Subject subject = SecurityUtils.getSubject(); + User user = subject.getPrincipals().oneByType(User.class); + String name = user.getDisplayName(); + String email = user.getMail(); + return new Person(name, email); + } + + public interface CommandWithAuthor { + Person getAuthor(); + + void setAuthor(Person person); + } +} diff --git a/scm-core/src/test/java/sonia/scm/repository/api/ModifyCommandBuilderTest.java b/scm-core/src/test/java/sonia/scm/repository/api/ModifyCommandBuilderTest.java index db9a247d65..6e46841cd2 100644 --- a/scm-core/src/test/java/sonia/scm/repository/api/ModifyCommandBuilderTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/api/ModifyCommandBuilderTest.java @@ -1,7 +1,6 @@ package sonia.scm.repository.api; import com.google.common.io.ByteSource; -import com.sun.org.apache.xpath.internal.operations.Bool; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -85,15 +84,6 @@ class ModifyCommandBuilderTest { verify(worker).delete("toBeDeleted"); } - @Test - void shouldExecuteMove() throws IOException { - initCommand() - .moveFile("source", "target") - .execute(); - - verify(worker).move("source", "target"); - } - @Test void shouldExecuteCreateWithByteSourceContent() throws IOException { ArgumentCaptor nameCaptor = ArgumentCaptor.forClass(String.class); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java index f87dc038c0..83ca05fb03 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java @@ -8,9 +8,8 @@ import org.eclipse.jgit.attributes.FilterCommandRegistry; import org.eclipse.jgit.revwalk.RevCommit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.BadRequestException; import sonia.scm.ConcurrentModificationException; -import sonia.scm.ContextEntry; +import sonia.scm.NoChangesMadeException; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; @@ -18,18 +17,10 @@ import sonia.scm.web.lfs.LfsBlobStoreFactory; import java.io.File; import java.io.IOException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.Optional; import java.util.concurrent.locks.Lock; -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; -import static sonia.scm.AlreadyExistsException.alreadyExists; -import static sonia.scm.ContextEntry.ContextBuilder.entity; -import static sonia.scm.NotFoundException.notFound; - public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand { private static final Logger LOG = LoggerFactory.getLogger(GitModifyCommand.class); @@ -49,7 +40,7 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman return inClone(clone -> new ModifyWorker(clone, request), workdirFactory, request.getBranch()); } - private class ModifyWorker extends GitCloneWorker implements Worker { + private class ModifyWorker extends GitCloneWorker implements ModifyWorkerHelper { private final File workDir; private final ModifyCommandRequest request; @@ -71,39 +62,15 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman for (ModifyCommandRequest.PartialRequest r : request.getRequests()) { r.execute(this); } - failIfNotChanged(NoChangesMadeException::new); + failIfNotChanged(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())); Optional revCommit = doCommit(request.getCommitMessage(), request.getAuthor()); push(); - return revCommit.orElseThrow(NoChangesMadeException::new).name(); + return revCommit.orElseThrow(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())).name(); } @Override - public void create(String toBeCreated, File file, boolean overwrite) throws IOException { - Path targetFile = new File(workDir, toBeCreated).toPath(); - createDirectories(targetFile); - if (overwrite) { - Files.move(file.toPath(), targetFile, REPLACE_EXISTING); - } else { - try { - Files.move(file.toPath(), targetFile); - } catch (FileAlreadyExistsException e) { - throw alreadyExists(createFileContext(toBeCreated)); - } - } - - addToGitWithLfsSupport(toBeCreated, targetFile); - } - - @Override - public void modify(String path, File file) throws IOException { - Path targetFile = new File(workDir, path).toPath(); - createDirectories(targetFile); - if (!targetFile.toFile().exists()) { - throw notFound(createFileContext(path)); - } - Files.move(file.toPath(), targetFile, REPLACE_EXISTING); - - addToGitWithLfsSupport(path, targetFile); + public void addFileToScm(String name, Path file) { + addToGitWithLfsSupport(name, file); } private void addToGitWithLfsSupport(String path, Path targetFile) { @@ -132,13 +99,7 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman } @Override - public void delete(String toBeDeleted) throws IOException { - Path fileToBeDeleted = new File(workDir, toBeDeleted).toPath(); - try { - Files.delete(fileToBeDeleted); - } catch (NoSuchFileException e) { - throw notFound(createFileContext(toBeDeleted)); - } + public void doScmDelete(String toBeDeleted) { try { getClone().rm().addFilepattern(removeStartingPathSeparators(toBeDeleted)).call(); } catch (GitAPIException e) { @@ -146,45 +107,27 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman } } + @Override + public File getWorkDir() { + return workDir; + } + + @Override + public Repository getRepository() { + return repository; + } + + @Override + public String getBranch() { + return request.getBranch(); + } + private String removeStartingPathSeparators(String path) { while (path.startsWith(File.separator)) { path = path.substring(1); } return path; } - - private void createDirectories(Path targetFile) throws IOException { - try { - Files.createDirectories(targetFile.getParent()); - } catch (FileAlreadyExistsException e) { - throw alreadyExists(createFileContext(targetFile.toString())); - } - } - - private ContextEntry.ContextBuilder createFileContext(String path) { - ContextEntry.ContextBuilder contextBuilder = entity("file", path); - if (!StringUtils.isEmpty(request.getBranch())) { - contextBuilder.in("branch", request.getBranch()); - } - contextBuilder.in(context.getRepository()); - return contextBuilder; - } - - @Override - public void move(String sourcePath, String targetPath) { - - } - - private class NoChangesMadeException extends BadRequestException { - public NoChangesMadeException() { - super(ContextEntry.ContextBuilder.entity(context.getRepository()).build(), "no changes detected to branch " + ModifyWorker.this.request.getBranch()); - } - - @Override - public String getCode() { - return "40RaYIeeR1"; - } - } } private String throwInternalRepositoryException(String message, Exception e) { diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java new file mode 100644 index 0000000000..0294b6902b --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgModifyCommand.java @@ -0,0 +1,103 @@ +package sonia.scm.repository.spi; + +import com.aragost.javahg.Changeset; +import com.aragost.javahg.Repository; +import com.aragost.javahg.commands.CommitCommand; +import com.aragost.javahg.commands.ExecutionException; +import com.aragost.javahg.commands.PullCommand; +import com.aragost.javahg.commands.RemoveCommand; +import com.aragost.javahg.commands.StatusCommand; +import sonia.scm.NoChangesMadeException; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.util.WorkingCopy; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +public class HgModifyCommand implements ModifyCommand { + + private HgCommandContext context; + private final HgWorkdirFactory workdirFactory; + + public HgModifyCommand(HgCommandContext context, HgWorkdirFactory workdirFactory) { + this.context = context; + this.workdirFactory = workdirFactory; + } + + @Override + public String execute(ModifyCommandRequest request) { + + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context, request.getBranch())) { + Repository workingRepository = workingCopy.getWorkingRepository(); + request.getRequests().forEach( + partialRequest -> { + try { + partialRequest.execute(new ModifyWorkerHelper() { + + @Override + public void addFileToScm(String name, Path file) { + try { + addFileToHg(file.toFile()); + } catch (ExecutionException e) { + throwInternalRepositoryException("could not add new file to index", e); + } + } + + @Override + public void doScmDelete(String toBeDeleted) { + RemoveCommand.on(workingRepository).execute(toBeDeleted); + } + + @Override + public sonia.scm.repository.Repository getRepository() { + return context.getScmRepository(); + } + + @Override + public String getBranch() { + return request.getBranch(); + } + + public File getWorkDir() { + return workingRepository.getDirectory(); + } + + private void addFileToHg(File file) { + workingRepository.workingCopy().add(file.getAbsolutePath()); + } + }); + } catch (IOException e) { + throwInternalRepositoryException("could not execute command on repository", e); + } + } + ); + if (StatusCommand.on(workingRepository).lines().isEmpty()) { + throw new NoChangesMadeException(context.getScmRepository()); + } + CommitCommand.on(workingRepository).user(String.format("%s <%s>", request.getAuthor().getName(), request.getAuthor().getMail())).message(request.getCommitMessage()).execute(); + List execute = pullModifyChangesToCentralRepository(request, workingCopy); + return execute.get(0).getNode(); + } catch (ExecutionException e) { + throwInternalRepositoryException("could not execute command on repository", e); + return null; + } + } + + private List pullModifyChangesToCentralRepository(ModifyCommandRequest request, WorkingCopy workingCopy) { + try { + com.aragost.javahg.commands.PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository()); + workdirFactory.configure(pullCommand); + return pullCommand.execute(workingCopy.getDirectory().getAbsolutePath()); + } catch (Exception e) { + throw new IntegrateChangesFromWorkdirException(context.getScmRepository(), + String.format("Could not pull modify changes from working copy to central repository for branch %s", request.getBranch()), + e); + } + } + + private String throwInternalRepositoryException(String message, Exception e) { + throw new InternalRepositoryException(context.getScmRepository(), message, e); + } +} 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..c80699add8 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 @@ -66,7 +66,8 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider Command.INCOMING, Command.OUTGOING, Command.PUSH, - Command.PULL + Command.PULL, + Command.MODIFY ); //J+ @@ -77,7 +78,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider //~--- constructors --------------------------------------------------------- HgRepositoryServiceProvider(HgRepositoryHandler handler, - HgHookManager hookManager, Repository repository) + HgHookManager hookManager, Repository repository) { this.repository = repository; this.handler = handler; @@ -238,6 +239,11 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider return new HgPushCommand(handler, context, repository); } + @Override + public ModifyCommand getModifyCommand() { + return new HgModifyCommand(context, handler.getWorkdirFactory()); + } + /** * Method description * 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..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 @@ -47,12 +47,12 @@ import sonia.scm.repository.Repository; public class HgRepositoryServiceResolver implements RepositoryServiceResolver { - private HgRepositoryHandler handler; - private HgHookManager hookManager; + private final HgRepositoryHandler handler; + private final HgHookManager hookManager; @Inject public HgRepositoryServiceResolver(HgRepositoryHandler handler, - HgHookManager hookManager) + HgHookManager hookManager) { this.handler = handler; this.hookManager = hookManager; diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py index c123660a83..1871200389 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py @@ -1,8 +1,8 @@ # # Copyright (c) 2010, Sebastian Sdorra -# All rights reserved. +# aLL rights reserved. # -# Redistribution and use in source and binary forms, with or without +# 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, @@ -34,7 +34,7 @@ Prints date, size and last message of files. """ from collections import defaultdict -from mercurial import cmdutil,util +from mercurial import scmutil cmdtable = {} @@ -122,7 +122,7 @@ class File_Object: return result class File_Walker: - + def __init__(self, sub_repositories, visitor): self.visitor = visitor self.sub_repositories = sub_repositories @@ -273,7 +273,7 @@ class File_Viewer: ('t', 'transport', False, 'format the output for command server'), ]) def fileview(ui, repo, **opts): - revCtx = repo[opts["revision"]] + revCtx = scmutil.revsingle(repo, opts["revision"]) subrepos = {} if not opts["disableSubRepositoryDetection"]: subrepos = collect_sub_repositories(revCtx) diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java new file mode 100644 index 0000000000..862d2ab2ed --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgModifyCommandTest.java @@ -0,0 +1,164 @@ +package sonia.scm.repository.spi; + +import com.google.inject.util.Providers; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import sonia.scm.AlreadyExistsException; +import sonia.scm.NoChangesMadeException; +import sonia.scm.NotFoundException; +import sonia.scm.repository.HgHookManager; +import sonia.scm.repository.HgTestUtil; +import sonia.scm.repository.Person; +import sonia.scm.repository.util.WorkdirProvider; +import sonia.scm.web.HgRepositoryEnvironmentBuilder; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HgModifyCommandTest extends AbstractHgCommandTestBase { + + private HgModifyCommand hgModifyCommand; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Before + public void initHgModifyCommand() { + HgHookManager hookManager = HgTestUtil.createHookManager(); + HgRepositoryEnvironmentBuilder environmentBuilder = new HgRepositoryEnvironmentBuilder(handler, hookManager); + SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(environmentBuilder), new WorkdirProvider()) { + @Override + public void configure(com.aragost.javahg.commands.PullCommand pullCommand) { + // we do not want to configure http hooks in this unit test + } + }; + hgModifyCommand = new HgModifyCommand(cmdContext, workdirFactory + ); + } + + @Test + public void shouldRemoveFiles() { + ModifyCommandRequest request = new ModifyCommandRequest(); + request.addRequest(new ModifyCommandRequest.DeleteFileRequest("a.txt")); + request.setCommitMessage("this is great"); + request.setAuthor(new Person("Arthur Dent", "dent@hitchhiker.com")); + + String result = hgModifyCommand.execute(request); + + assertThat(cmdContext.open().tip().getNode()).isEqualTo(result); + } + + @Test + public void shouldCreateFilesWithoutOverwrite() throws IOException { + File testFile = temporaryFolder.newFile(); + + ModifyCommandRequest request = new ModifyCommandRequest(); + request.addRequest(new ModifyCommandRequest.CreateFileRequest("Answer.txt", testFile, false)); + request.setCommitMessage("I found the answer"); + request.setAuthor(new Person("Trillian Astra", "trillian@hitchhiker.com")); + + String changeSet = hgModifyCommand.execute(request); + + assertThat(cmdContext.open().tip().getNode()).isEqualTo(changeSet); + assertThat(cmdContext.open().tip().getAddedFiles().size()).isEqualTo(1); + } + + @Test + public void shouldOverwriteExistingFiles() throws IOException { + File testFile = temporaryFolder.newFile(); + + new FileOutputStream(testFile).write(42); + ModifyCommandRequest request2 = new ModifyCommandRequest(); + request2.addRequest(new ModifyCommandRequest.CreateFileRequest("a.txt", testFile, true)); + request2.setCommitMessage(" Now i really found the answer"); + request2.setAuthor(new Person("Trillian Astra", "trillian@hitchhiker.com")); + + String changeSet2 = hgModifyCommand.execute(request2); + + assertThat(cmdContext.open().tip().getNode()).isEqualTo(changeSet2); + assertThat(cmdContext.open().tip().getModifiedFiles().size()).isEqualTo(1); + assertThat(cmdContext.open().tip().getModifiedFiles().get(0)).isEqualTo("a.txt"); + } + + @Test(expected = AlreadyExistsException.class) + public void shouldThrowFileAlreadyExistsException() throws IOException { + + File testFile = temporaryFolder.newFile(); + new FileOutputStream(testFile).write(21); + + ModifyCommandRequest request = new ModifyCommandRequest(); + request.addRequest(new ModifyCommandRequest.CreateFileRequest("Answer.txt", testFile, false)); + request.setCommitMessage("I found the answer"); + request.setAuthor(new Person("Trillian Astra", "trillian@hitchhiker.com")); + + hgModifyCommand.execute(request); + + new FileOutputStream(testFile).write(42); + ModifyCommandRequest request2 = new ModifyCommandRequest(); + request2.addRequest(new ModifyCommandRequest.CreateFileRequest("Answer.txt", testFile, false)); + request2.setCommitMessage(" Now i really found the answer"); + request2.setAuthor(new Person("Trillian Astra", "trillian@hitchhiker.com")); + + hgModifyCommand.execute(request2); + } + + @Test + public void shouldModifyExistingFile() throws IOException { + File testFile = temporaryFolder.newFile("a.txt"); + + new FileOutputStream(testFile).write(42); + ModifyCommandRequest request2 = new ModifyCommandRequest(); + request2.addRequest(new ModifyCommandRequest.ModifyFileRequest("a.txt", testFile)); + request2.setCommitMessage(" Now i really found the answer"); + request2.setAuthor(new Person("Trillian Astra", "trillian@hitchhiker.com")); + + String changeSet2 = hgModifyCommand.execute(request2); + + assertThat(cmdContext.open().tip().getNode()).isEqualTo(changeSet2); + assertThat(cmdContext.open().tip().getModifiedFiles().size()).isEqualTo(1); + assertThat(cmdContext.open().tip().getModifiedFiles().get(0)).isEqualTo(testFile.getName()); + } + + @Test(expected = NotFoundException.class) + public void shouldThrowNotFoundExceptionIfFileDoesNotExist() throws IOException { + File testFile = temporaryFolder.newFile("Answer.txt"); + + new FileOutputStream(testFile).write(42); + ModifyCommandRequest request2 = new ModifyCommandRequest(); + request2.addRequest(new ModifyCommandRequest.ModifyFileRequest("Answer.txt", testFile)); + request2.setCommitMessage(" Now i really found the answer"); + request2.setAuthor(new Person("Trillian Astra", "trillian@hitchhiker.com")); + + hgModifyCommand.execute(request2); + } + + @Test(expected = NullPointerException.class) + public void shouldThrowNPEIfAuthorIsMissing() throws IOException { + File testFile = temporaryFolder.newFile(); + + ModifyCommandRequest request = new ModifyCommandRequest(); + request.addRequest(new ModifyCommandRequest.CreateFileRequest("Answer.txt", testFile, false)); + request.setCommitMessage("I found the answer"); + hgModifyCommand.execute(request); + } + + @Test(expected = NullPointerException.class) + public void shouldThrowNPEIfCommitMessageIsMissing() throws IOException { + File testFile = temporaryFolder.newFile(); + + ModifyCommandRequest request = new ModifyCommandRequest(); + request.addRequest(new ModifyCommandRequest.CreateFileRequest("Answer.txt", testFile, false)); + request.setAuthor(new Person("Trillian Astra", "trillian@hitchhiker.com")); + hgModifyCommand.execute(request); + } + + @Test(expected = NoChangesMadeException.class) + public void shouldThrowNoChangesMadeExceptionIfEmptyLocalChangesetAfterRequest() { + hgModifyCommand.execute(new ModifyCommandRequest()); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MergeResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MergeResourceTest.java index b9e720fc71..202b2d20f1 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MergeResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MergeResourceTest.java @@ -3,6 +3,7 @@ package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.SubjectAware; import com.google.common.io.Resources; import com.google.inject.util.Providers; +import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; import org.jboss.resteasy.core.Dispatcher; @@ -24,6 +25,7 @@ import sonia.scm.repository.api.MergeDryRunCommandResult; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.repository.spi.MergeCommand; +import sonia.scm.user.User; import sonia.scm.web.VndMediaType; import java.net.URL; @@ -32,6 +34,7 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static sonia.scm.repository.RepositoryTestData.createHeartOfGold; @@ -105,6 +108,7 @@ public class MergeResourceTest extends RepositoryTestBase { @Test void shouldHandleSuccessfulMerge() throws Exception { when(mergeCommand.merge(any())).thenReturn(MergeCommandResult.success()); + mockUser(); URL url = Resources.getResource("sonia/scm/api/v2/mergeCommand.json"); byte[] mergeCommandJson = Resources.toByteArray(url); @@ -122,6 +126,7 @@ public class MergeResourceTest extends RepositoryTestBase { @Test void shouldHandleFailedMerge() throws Exception { when(mergeCommand.merge(any())).thenReturn(MergeCommandResult.failure(asList("file1", "file2"))); + mockUser(); URL url = Resources.getResource("sonia/scm/api/v2/mergeCommand.json"); byte[] mergeCommandJson = Resources.toByteArray(url); @@ -189,5 +194,12 @@ public class MergeResourceTest extends RepositoryTestBase { assertThat(response.getStatus()).isEqualTo(204); } + + + private void mockUser() { + PrincipalCollection collection = mock(PrincipalCollection.class); + when(subject.getPrincipals()).thenReturn(collection); + when(collection.oneByType(User.class)).thenReturn(new User("dummy")); + } } }