mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-10-31 18:46:07 +01:00
Introduce Git Revert functionality to SCM-Manager
This commit is contained in:
@@ -71,10 +71,10 @@ import static java.util.Optional.ofNullable;
|
||||
|
||||
public final class GitUtil {
|
||||
|
||||
private static final GitUserAgentProvider GIT_USER_AGENT_PROVIDER = new GitUserAgentProvider();
|
||||
public static final String REF_HEAD = "HEAD";
|
||||
public static final String REF_HEAD_PREFIX = "refs/heads/";
|
||||
public static final String REF_MAIN = "main";
|
||||
private static final GitUserAgentProvider GIT_USER_AGENT_PROVIDER = new GitUserAgentProvider();
|
||||
private static final String DIRECTORY_DOTGIT = ".git";
|
||||
private static final String DIRECTORY_OBJETCS = "objects";
|
||||
private static final String DIRECTORY_REFS = "refs";
|
||||
@@ -84,15 +84,13 @@ public final class GitUtil {
|
||||
private static final String REMOTE_REF = "refs/remote/scm/%s/%s";
|
||||
private static final int TIMEOUT = 5;
|
||||
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GitUtil.class);
|
||||
private static final String REF_SPEC = "refs/heads/*:refs/heads/*";
|
||||
|
||||
private static final String GPG_HEADER = "-----BEGIN PGP SIGNATURE-----";
|
||||
|
||||
private GitUtil() {
|
||||
}
|
||||
|
||||
|
||||
public static void close(org.eclipse.jgit.lib.Repository repo) {
|
||||
if (repo != null) {
|
||||
repo.close();
|
||||
@@ -181,7 +179,6 @@ public final class GitUtil {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String getBranch(Ref ref) {
|
||||
String branch = null;
|
||||
|
||||
@@ -234,7 +231,6 @@ public final class GitUtil {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Ref getBranchId(org.eclipse.jgit.lib.Repository repo,
|
||||
String branchName)
|
||||
throws IOException {
|
||||
@@ -291,22 +287,43 @@ public final class GitUtil {
|
||||
/**
|
||||
* Returns the commit for the given ref.
|
||||
* If the given ref is for a tag, the commit that this tag belongs to is returned instead.
|
||||
*
|
||||
* @param repository jgit repository
|
||||
* @param revWalk rev walk
|
||||
* @param ref commit/tag ref
|
||||
* @return {@link RevCommit}
|
||||
* @throws IOException exception
|
||||
*/
|
||||
public static RevCommit getCommit(org.eclipse.jgit.lib.Repository repository,
|
||||
RevWalk revWalk, Ref ref)
|
||||
throws IOException {
|
||||
RevCommit commit = null;
|
||||
ObjectId id = ref.getPeeledObjectId();
|
||||
|
||||
if (id == null) {
|
||||
id = ref.getObjectId();
|
||||
}
|
||||
|
||||
return getCommit(repository, revWalk, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the commit for the given object id. The id is expected to be a commit and not a tag.
|
||||
*
|
||||
* @param repository jgit repository
|
||||
* @param revWalk rev walk
|
||||
* @param id commit id
|
||||
* @return {@link RevCommit}
|
||||
* @throws IOException exception
|
||||
* @since 3.8.0
|
||||
*/
|
||||
public static RevCommit getCommit(org.eclipse.jgit.lib.Repository repository,
|
||||
RevWalk revWalk, ObjectId id) throws IOException {
|
||||
RevCommit commit = null;
|
||||
|
||||
if (id != null) {
|
||||
if (revWalk == null) {
|
||||
revWalk = new RevWalk(repository);
|
||||
}
|
||||
|
||||
commit = revWalk.parseCommit(id);
|
||||
}
|
||||
|
||||
@@ -330,7 +347,6 @@ public final class GitUtil {
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
||||
public static long getCommitTime(RevCommit commit) {
|
||||
long date = commit.getCommitTime();
|
||||
|
||||
@@ -339,7 +355,6 @@ public final class GitUtil {
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
public static String getId(AnyObjectId objectId) {
|
||||
String id = Util.EMPTY_STRING;
|
||||
|
||||
@@ -350,7 +365,6 @@ public final class GitUtil {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
public static Ref getRefForCommit(org.eclipse.jgit.lib.Repository repository,
|
||||
ObjectId id)
|
||||
throws IOException {
|
||||
@@ -415,7 +429,6 @@ public final class GitUtil {
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
|
||||
public static ObjectId getRevisionId(org.eclipse.jgit.lib.Repository repo,
|
||||
String revision)
|
||||
throws IOException {
|
||||
@@ -430,7 +443,6 @@ public final class GitUtil {
|
||||
return revId;
|
||||
}
|
||||
|
||||
|
||||
public static String getScmRemoteRefName(Repository repository,
|
||||
Ref localBranch) {
|
||||
return getScmRemoteRefName(repository, localBranch.getName());
|
||||
@@ -463,7 +475,6 @@ public final class GitUtil {
|
||||
return tagName;
|
||||
}
|
||||
|
||||
|
||||
public static String getTagName(Ref ref) {
|
||||
String name = ref.getName();
|
||||
|
||||
@@ -474,8 +485,6 @@ public final class GitUtil {
|
||||
return name;
|
||||
}
|
||||
|
||||
private static final String GPG_HEADER = "-----BEGIN PGP SIGNATURE-----";
|
||||
|
||||
public static Optional<Signature> getTagSignature(RevObject revObject, GPG gpg, RevWalk revWalk) throws IOException {
|
||||
if (revObject instanceof RevTag) {
|
||||
final byte[] messageBytes = revWalk.getObjectReader().open(revObject.getId()).getBytes();
|
||||
|
||||
@@ -49,7 +49,8 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||
Command.MIRROR,
|
||||
Command.FILE_LOCK,
|
||||
Command.BRANCH_DETAILS,
|
||||
Command.CHANGESETS
|
||||
Command.CHANGESETS,
|
||||
Command.REVERT
|
||||
);
|
||||
|
||||
protected static final Set<Feature> FEATURES = EnumSet.of(
|
||||
@@ -184,6 +185,11 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider {
|
||||
return injector.getInstance(GitChangesetsCommand.Factory.class).create(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RevertCommand getRevertCommand() {
|
||||
return injector.getInstance(GitRevertCommand.Factory.class).create(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Command> getSupportedCommands() {
|
||||
return COMMANDS;
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.jgit.api.errors.CanceledException;
|
||||
import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.merge.MergeStrategy;
|
||||
import org.eclipse.jgit.merge.RecursiveMerger;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import sonia.scm.NoChangesMadeException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.MultipleParentsNotAllowedException;
|
||||
import sonia.scm.repository.NoParentException;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.api.RevertCommandResult;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
@Slf4j
|
||||
public class GitRevertCommand extends AbstractGitCommand implements RevertCommand {
|
||||
|
||||
private final RepositoryManager repositoryManager;
|
||||
private final GitRepositoryHookEventFactory eventFactory;
|
||||
|
||||
@Inject
|
||||
GitRevertCommand(@Assisted GitContext context, RepositoryManager repositoryManager, GitRepositoryHookEventFactory eventFactory) {
|
||||
super(context);
|
||||
this.repositoryManager = repositoryManager;
|
||||
this.eventFactory = eventFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RevertCommandResult revert(RevertCommandRequest request) {
|
||||
log.debug("revert {} on {} in repository {}",
|
||||
request.getRevision(),
|
||||
request.getBranch().orElse("default branch"),
|
||||
repository.getName());
|
||||
|
||||
try (Repository jRepository = context.open();
|
||||
RevWalk revWalk = new RevWalk(jRepository)) {
|
||||
|
||||
ObjectId sourceRevision = getSourceRevision(request, jRepository, repository);
|
||||
ObjectId targetRevision = getTargetRevision(request, jRepository, repository);
|
||||
|
||||
RevCommit parent = getParentRevision(revWalk, sourceRevision, jRepository);
|
||||
|
||||
RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(jRepository, true);
|
||||
merger.setBase(sourceRevision);
|
||||
|
||||
boolean mergeSucceeded = merger.merge(targetRevision, parent);
|
||||
|
||||
if (!mergeSucceeded) {
|
||||
log.info("revert merge fail: {} on {} in repository {}",
|
||||
sourceRevision.getName(), targetRevision.getName(), repository.getName());
|
||||
return RevertCommandResult.failure(MergeHelper.getFailingPaths(merger));
|
||||
}
|
||||
|
||||
ObjectId oldTreeId = revWalk.parseCommit(targetRevision).getTree().toObjectId();
|
||||
ObjectId newTreeId = merger.getResultTreeId();
|
||||
if (oldTreeId.equals(newTreeId)) {
|
||||
throw new NoChangesMadeException(repository);
|
||||
}
|
||||
|
||||
log.debug("revert {} on {} in repository {} successful, preparing commit",
|
||||
sourceRevision.getName(), targetRevision.getName(), repository.getName());
|
||||
CommitHelper commitHelper = new CommitHelper(context, repositoryManager, eventFactory);
|
||||
ObjectId commitId = commitHelper.createCommit(
|
||||
newTreeId,
|
||||
request.getAuthor(),
|
||||
request.getAuthor(),
|
||||
determineMessage(request, GitUtil.getCommit(jRepository, revWalk, sourceRevision)),
|
||||
request.isSign(),
|
||||
targetRevision
|
||||
);
|
||||
|
||||
commitHelper.updateBranch(
|
||||
request.getBranch().orElseGet(() -> context.getConfig().getDefaultBranch()), commitId, targetRevision
|
||||
);
|
||||
|
||||
return RevertCommandResult.success(commitId.getName());
|
||||
|
||||
} catch (CanceledException | IOException | UnsupportedSigningFormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectId getSourceRevision(RevertCommandRequest request,
|
||||
Repository jRepository,
|
||||
sonia.scm.repository.Repository sRepository) throws IOException {
|
||||
ObjectId sourceRevision = GitUtil.getRevisionId(jRepository, request.getRevision());
|
||||
|
||||
if (sourceRevision == null) {
|
||||
log.error("source revision not found!");
|
||||
throw NotFoundException.notFound(entity(ObjectId.class, request.getRevision()).in(sRepository));
|
||||
}
|
||||
|
||||
log.debug("got source revision {} for repository {}", sourceRevision.getName(), jRepository.getIdentifier());
|
||||
return sourceRevision;
|
||||
}
|
||||
|
||||
private ObjectId getTargetRevision(RevertCommandRequest request,
|
||||
Repository jRepository,
|
||||
sonia.scm.repository.Repository sRepository) throws IOException {
|
||||
if (request.getBranch().isEmpty() || request.getBranch().get().isEmpty()) {
|
||||
ObjectId targetRevision = GitUtil.getRepositoryHead(jRepository);
|
||||
log.debug("given target branch is empty, returning HEAD revision for repository {}", jRepository.getIdentifier());
|
||||
return targetRevision;
|
||||
}
|
||||
|
||||
ObjectId targetRevision = GitUtil.getRevisionId(jRepository, request.getBranch().get());
|
||||
if (targetRevision == null) {
|
||||
log.error("target revision not found!");
|
||||
throw NotFoundException.notFound(entity(ObjectId.class, request.getBranch().get()).in(sRepository));
|
||||
}
|
||||
|
||||
log.debug("got target revision {} for repository {}", targetRevision.getName(), jRepository.getIdentifier());
|
||||
return targetRevision;
|
||||
}
|
||||
|
||||
private RevCommit getParentRevision(RevWalk revWalk, ObjectId sourceRevision, Repository jRepository) throws IOException {
|
||||
RevCommit source = revWalk.parseCommit(sourceRevision);
|
||||
int sourceParents = source.getParentCount();
|
||||
|
||||
if (sourceParents == 0) {
|
||||
throw new NoParentException(sourceRevision.getName());
|
||||
} else if (sourceParents > 1) {
|
||||
throw new MultipleParentsNotAllowedException(sourceRevision.getName());
|
||||
}
|
||||
|
||||
RevCommit parent = source.getParent(0);
|
||||
|
||||
log.debug("got parent revision {} of revision {} for repository {}", parent.getName(), sourceRevision.getName(), jRepository.getIdentifier());
|
||||
return parent;
|
||||
}
|
||||
|
||||
private String determineMessage(RevertCommandRequest request, RevCommit revertedCommit) {
|
||||
return request.getMessage().orElseGet(() -> {
|
||||
log.debug("no custom message given, choose default message");
|
||||
return String.format("""
|
||||
Revert "%s"
|
||||
|
||||
This reverts commit %s.""", revertedCommit.getShortMessage(), revertedCommit.getId().getName());
|
||||
});
|
||||
}
|
||||
|
||||
public interface Factory {
|
||||
RevertCommand create(GitContext context);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import org.eclipse.jgit.api.errors.CanceledException;
|
||||
import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.merge.RecursiveMerger;
|
||||
import org.eclipse.jgit.merge.ResolveMerger;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
@@ -80,6 +81,15 @@ class MergeHelper {
|
||||
this.message = request.getMessage();
|
||||
}
|
||||
|
||||
static Collection<String> getFailingPaths(ResolveMerger merger) {
|
||||
return merger.getMergeResults()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue().containsConflicts())
|
||||
.map(Map.Entry::getKey)
|
||||
.toList();
|
||||
}
|
||||
|
||||
ObjectId getTargetRevision() {
|
||||
return targetRevision;
|
||||
}
|
||||
@@ -107,15 +117,6 @@ class MergeHelper {
|
||||
}
|
||||
}
|
||||
|
||||
Collection<String> getFailingPaths(ResolveMerger merger) {
|
||||
return merger.getMergeResults()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue().containsConflicts())
|
||||
.map(Map.Entry::getKey)
|
||||
.toList();
|
||||
}
|
||||
|
||||
boolean isMergedInto(ObjectId baseRevision, ObjectId revisionToCheck) {
|
||||
try (RevWalk revWalk = new RevWalk(context.open())) {
|
||||
RevCommit baseCommit = revWalk.parseCommit(baseRevision);
|
||||
|
||||
@@ -57,6 +57,7 @@ import sonia.scm.repository.spi.GitModifyCommand;
|
||||
import sonia.scm.repository.spi.GitOutgoingCommand;
|
||||
import sonia.scm.repository.spi.GitPullCommand;
|
||||
import sonia.scm.repository.spi.GitPushCommand;
|
||||
import sonia.scm.repository.spi.GitRevertCommand;
|
||||
import sonia.scm.repository.spi.GitTagCommand;
|
||||
import sonia.scm.repository.spi.GitTagsCommand;
|
||||
import sonia.scm.repository.spi.GitUnbundleCommand;
|
||||
@@ -70,6 +71,7 @@ import sonia.scm.repository.spi.OutgoingCommand;
|
||||
import sonia.scm.repository.spi.PostReceiveRepositoryHookEventFactory;
|
||||
import sonia.scm.repository.spi.PullCommand;
|
||||
import sonia.scm.repository.spi.PushCommand;
|
||||
import sonia.scm.repository.spi.RevertCommand;
|
||||
import sonia.scm.repository.spi.SimpleGitWorkingCopyFactory;
|
||||
import sonia.scm.repository.spi.TagCommand;
|
||||
import sonia.scm.repository.spi.TagsCommand;
|
||||
@@ -119,7 +121,6 @@ public class GitServletModule extends ServletModule {
|
||||
install(new FactoryModuleBuilder().implement(FileLockCommand.class, GitFileLockCommand.class).build(GitFileLockCommand.Factory.class));
|
||||
install(new FactoryModuleBuilder().implement(BranchDetailsCommand.class, GitBranchDetailsCommand.class).build(GitBranchDetailsCommand.Factory.class));
|
||||
install(new FactoryModuleBuilder().implement(ChangesetsCommand.class, GitChangesetsCommand.class).build(GitChangesetsCommand.Factory.class));
|
||||
|
||||
|
||||
install(new FactoryModuleBuilder().implement(RevertCommand.class, GitRevertCommand.class).build(GitRevertCommand.Factory.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,53 +18,49 @@ package sonia.scm.repository.spi;
|
||||
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
import sonia.scm.store.InMemoryByteConfigurationStoreFactory;
|
||||
|
||||
|
||||
public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase
|
||||
{
|
||||
public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase {
|
||||
|
||||
@After
|
||||
public void close()
|
||||
{
|
||||
private GitContext context;
|
||||
|
||||
@After
|
||||
@AfterEach
|
||||
public void close() {
|
||||
if (context != null) {
|
||||
context.setConfig(new GitRepositoryConfig());
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected GitContext createContext()
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
protected GitContext createContext() {
|
||||
return createContext("main");
|
||||
}
|
||||
|
||||
protected GitContext createContext(String defaultBranch) {
|
||||
if (context == null) {
|
||||
GitConfig config = new GitConfig();
|
||||
config.setDefaultBranch("master");
|
||||
context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create()), config);
|
||||
config.setDefaultBranch(defaultBranch);
|
||||
GitRepositoryConfigStoreProvider storeProvider = new GitRepositoryConfigStoreProvider(new InMemoryByteConfigurationStoreFactory());
|
||||
storeProvider.setDefaultBranch(repository, defaultBranch);
|
||||
context = new GitContext(repositoryDirectory, repository, storeProvider, config);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected String getType()
|
||||
{
|
||||
protected String getType() {
|
||||
return "git";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String getZippedRepositoryResource()
|
||||
{
|
||||
protected String getZippedRepositoryResource() {
|
||||
return "sonia/scm/repository/spi/scm-git-spi-test.zip";
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
private GitContext context;
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ public class GitBranchesCommandTest extends AbstractGitCommandTestBase {
|
||||
|
||||
List<Branch> branches = branchesCommand.getBranches();
|
||||
|
||||
assertThat(findBranch(branches, "master")).isEqualTo(
|
||||
assertThat(findBranch(branches, "main")).isEqualTo(
|
||||
defaultBranch(
|
||||
"master",
|
||||
"main",
|
||||
"fcd0ef1831e4002ac43ea539f4094334c79ea9ec",
|
||||
1339428655000L,
|
||||
new Person("Zaphod Beeblebrox", "zaphod.beeblebrox@hitchhiker.com")
|
||||
|
||||
@@ -44,7 +44,7 @@ public class GitBrowseCommand_BrokenSubmoduleTest extends AbstractGitCommandTest
|
||||
|
||||
@Before
|
||||
public void createCommand() {
|
||||
command = new GitBrowseCommand(createContext(), lfsBlobStoreFactory, synchronousExecutor());
|
||||
command = new GitBrowseCommand(createContext("master"), lfsBlobStoreFactory, synchronousExecutor());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -71,6 +71,6 @@ public class GitBrowseCommand_RecursiveDirectoryNameTest extends AbstractGitComm
|
||||
}
|
||||
|
||||
private GitBrowseCommand createCommand() {
|
||||
return new GitBrowseCommand(createContext(), lfsBlobStoreFactory, synchronousExecutor());
|
||||
return new GitBrowseCommand(createContext("master"), lfsBlobStoreFactory, synchronousExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
|
||||
assertEquals("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1", result.getChangesets().get(1).getId());
|
||||
assertEquals("592d797cd36432e591416e8b2b98154f4f163411", result.getChangesets().get(2).getId());
|
||||
assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", result.getChangesets().get(3).getId());
|
||||
assertEquals("master", result.getBranchName());
|
||||
assertEquals("main", result.getBranchName());
|
||||
assertTrue(result.getChangesets().stream().allMatch(r -> r.getBranches().isEmpty()));
|
||||
|
||||
// set default branch and fetch again
|
||||
@@ -271,15 +271,6 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
|
||||
assertEquals("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4", c.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindDefaultBranchFromHEAD() throws Exception {
|
||||
setRepositoryHeadReference("ref: refs/heads/test-branch");
|
||||
|
||||
ChangesetPagingResult changesets = createCommand().getChangesets(new LogCommandRequest());
|
||||
|
||||
assertEquals("test-branch", changesets.getBranchName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindMasterBranchWhenHEADisNoRef() throws Exception {
|
||||
setRepositoryHeadReference("592d797cd36432e591416e8b2b98154f4f163411");
|
||||
|
||||
@@ -67,7 +67,7 @@ class GitModifyCommandTestBase extends AbstractGitCommandTestBase {
|
||||
RepositoryHookEvent postReceiveEvent = mockEvent(POST_RECEIVE);
|
||||
when(eventFactory.createPostReceiveEvent(any(), any(), any(), any())).thenReturn(postReceiveEvent);
|
||||
return new GitModifyCommand(
|
||||
createContext(),
|
||||
createContext("master"),
|
||||
lfsBlobStoreFactory,
|
||||
repositoryManager,
|
||||
eventFactory
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.eclipse.jgit.lib.GpgConfig;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.lib.Signers;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.github.sdorra.jse.ShiroExtension;
|
||||
import org.github.sdorra.jse.SubjectAware;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
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.NoChangesMadeException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.GitTestHelper;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.MultipleParentsNotAllowedException;
|
||||
import sonia.scm.repository.NoParentException;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.api.RevertCommandResult;
|
||||
import sonia.scm.user.User;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
@ExtendWith({MockitoExtension.class, ShiroExtension.class})
|
||||
class GitRevertCommandTest extends AbstractGitCommandTestBase {
|
||||
|
||||
static final String HEAD_REVISION = "18e22df410df66f027dc49bf0f229f4b9efb8ce5";
|
||||
static final String HEAD_MINUS_0_REVISION = "9d39c9f59030fd4e3d37e1d3717bcca43a9a5eef";
|
||||
static final String CONFLICTING_TARGET_BRANCH = "conflictingTargetBranch";
|
||||
static final String CONFLICTING_SOURCE_REVISION = "0d5be1f22687d75916c82ce10eb592375ba0fb21";
|
||||
static final String PARENTLESS_REVISION = "190bc4670197edeb724f0ee1e49d3a5307635228";
|
||||
static final String DIVERGING_BRANCH = "divergingBranch";
|
||||
static final String DIVERGING_MAIN_LATEST_ANCESTOR = "0d5be1f22687d75916c82ce10eb592375ba0fb21";
|
||||
static final String DIVERGING_BRANCH_LATEST_COMMIT = "e77fd7c8cd45be992e19a6d22170ead4fcd5f9ce";
|
||||
static final String MERGED_REVISION = "00da9cca94a507346c5b8284983f8a69840cc277";
|
||||
|
||||
@Mock
|
||||
RepositoryManager repositoryManager;
|
||||
@Mock
|
||||
GitRepositoryHookEventFactory gitRepositoryHookEventFactory;
|
||||
|
||||
@Override
|
||||
protected String getZippedRepositoryResource() {
|
||||
return "sonia/scm/repository/spi/scm-git-spi-revert-test.zip";
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Revert {
|
||||
|
||||
@BeforeAll
|
||||
public static void setSigner() {
|
||||
Signers.set(GpgConfig.GpgFormat.OPENPGP, new GitTestHelper.SimpleGpgSigner());
|
||||
}
|
||||
|
||||
/**
|
||||
* We expect the newly created revision to be merged into the given branch.
|
||||
*/
|
||||
@Test
|
||||
void shouldBeTipOfHeadBranchAfterRevert() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
try (
|
||||
GitContext context = createContext();
|
||||
Repository jRepository = context.open()) {
|
||||
assertThat(GitUtil.getBranchId(jRepository, "main").getObjectId().getName()).isEqualTo(result.getRevision());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBeTipOfDifferentBranchAfterRevert() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(DIVERGING_MAIN_LATEST_ANCESTOR);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
request.setBranch(DIVERGING_BRANCH);
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
try (
|
||||
GitContext context = createContext();
|
||||
Repository jRepository = context.open()) {
|
||||
assertThat(GitUtil.getBranchId(jRepository, DIVERGING_BRANCH).getObjectId().getName()).isEqualTo(result.getRevision());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRevertWithoutChange() {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
|
||||
command.revert(request);
|
||||
|
||||
assertThrows(NoChangesMadeException.class, () -> command.revert(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverting this very commit.
|
||||
*/
|
||||
@Test
|
||||
void shouldRevertHeadCommit() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
request.setBranch("main");
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
assertThat(result.isSuccessful()).isTrue();
|
||||
try (GitContext context = createContext()) {
|
||||
GitDiffCommand diffCommand = new GitDiffCommand(context);
|
||||
DiffCommandRequest diffRequest = new DiffCommandRequest();
|
||||
diffRequest.setRevision(result.getRevision());
|
||||
diffRequest.setPath("hitchhiker");
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
diffCommand.getDiffResult(diffRequest).accept(baos);
|
||||
assertThat(baos.toString()).contains("George Lucas\n-Darth Vader");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverting this very commit.
|
||||
* The branch is not explicitly set, so we expect the default branch.
|
||||
*/
|
||||
@Test
|
||||
void shouldRevertHeadCommitImplicitly() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
assertThat(result.isSuccessful()).isTrue();
|
||||
try (GitContext context = createContext()) {
|
||||
GitDiffCommand diffCommand = new GitDiffCommand(context);
|
||||
DiffCommandRequest diffRequest = new DiffCommandRequest();
|
||||
diffRequest.setRevision(result.getRevision());
|
||||
diffRequest.setPath("hitchhiker");
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
diffCommand.getDiffResult(diffRequest).accept(baos);
|
||||
assertThat(baos.toString()).contains("George Lucas\n-Darth Vader");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverting a change from one commit ago.
|
||||
*/
|
||||
@Test
|
||||
void shouldRevertPreviousHistoryCommit() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_MINUS_0_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
request.setBranch("main");
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
assertThat(result.isSuccessful()).isTrue();
|
||||
try (GitContext context = createContext()) {
|
||||
GitDiffCommand diffCommand = new GitDiffCommand(context);
|
||||
DiffCommandRequest diffRequest = new DiffCommandRequest();
|
||||
diffRequest.setRevision(result.getRevision());
|
||||
diffRequest.setPath("kerbal");
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
diffCommand.getDiffResult(diffRequest).accept(baos);
|
||||
assertThat(baos.toString()).contains("-deathstar\n+kerbin");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRevertCommitOnDifferentBranch() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(DIVERGING_MAIN_LATEST_ANCESTOR);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
request.setBranch(DIVERGING_BRANCH);
|
||||
RevertCommandResult result = command.revert(request);
|
||||
assertThat(result.isSuccessful()).isTrue();
|
||||
|
||||
try (
|
||||
GitContext context = createContext();
|
||||
Repository jRepository = context.open();
|
||||
RevWalk revWalk = new RevWalk(jRepository)) {
|
||||
ObjectId objectId = GitUtil.getRevisionId(jRepository, result.getRevision());
|
||||
RevCommit commit = GitUtil.getCommit(jRepository, revWalk, objectId);
|
||||
assertThat(commit.getParent(0).getName()).isEqualTo(DIVERGING_BRANCH_LATEST_COMMIT);
|
||||
|
||||
GitDiffCommand diffCommand = new GitDiffCommand(context);
|
||||
DiffCommandRequest diffRequest = new DiffCommandRequest();
|
||||
|
||||
diffRequest.setRevision(result.getRevision());
|
||||
diffRequest.setAncestorChangeset(DIVERGING_BRANCH_LATEST_COMMIT);
|
||||
diffRequest.setPath("hitchhiker");
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
diffCommand.getDiffResult(diffRequest).accept(baos);
|
||||
assertThat(baos.toString()).contains("""
|
||||
-George Lucas
|
||||
+Douglas Adams"""
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRevertTwiceOnDiffHeads() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_MINUS_0_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
request.setBranch("main");
|
||||
RevertCommandResult result1 = command.revert(request);
|
||||
|
||||
assertThat(result1.isSuccessful()).isTrue();
|
||||
|
||||
request.setRevision(result1.getRevision());
|
||||
RevertCommandResult result2 = command.revert(request);
|
||||
|
||||
assertThat(result2.isSuccessful()).isTrue();
|
||||
|
||||
try (GitContext context = createContext()) {
|
||||
GitDiffCommand diffCommand = new GitDiffCommand(context);
|
||||
DiffCommandRequest diffRequest = new DiffCommandRequest();
|
||||
|
||||
// Check against original head; should be the same
|
||||
diffRequest.setRevision(HEAD_REVISION);
|
||||
diffRequest.setPath("kerbal");
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
diffCommand.getDiffResult(diffRequest).accept(baos);
|
||||
// no difference, thus empty
|
||||
assertThat(baos.toString()).isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReportCorrectFilesAfterMergeConflict() {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(CONFLICTING_SOURCE_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
request.setBranch(CONFLICTING_TARGET_BRANCH);
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
assertThat(result.isSuccessful()).isFalse();
|
||||
assertThat(result.getFilesWithConflict()).containsExactly("hitchhiker");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSetCustomMessageIfGiven() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
request.setBranch("main");
|
||||
request.setMessage("I will never join you!");
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
assertThat(result.isSuccessful()).isTrue();
|
||||
|
||||
try (
|
||||
GitContext context = createContext();
|
||||
Repository jRepository = context.open();
|
||||
RevWalk revWalk = new RevWalk(jRepository)) {
|
||||
ObjectId objectId = GitUtil.getRevisionId(jRepository, result.getRevision());
|
||||
RevCommit commit = GitUtil.getCommit(jRepository, revWalk, objectId);
|
||||
assertThat(commit.getShortMessage()).isEqualTo("I will never join you!");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSetDefaultMessageIfNoCustomMessageGiven() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
request.setBranch("main");
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
try (
|
||||
GitContext context = createContext();
|
||||
Repository jRepository = context.open();
|
||||
RevWalk revWalk = new RevWalk(jRepository)) {
|
||||
|
||||
ObjectId revertedCommitId = GitUtil.getRevisionId(jRepository, request.getRevision());
|
||||
RevCommit revertedCommit = GitUtil.getCommit(jRepository, revWalk, revertedCommitId);
|
||||
ObjectId newCommitId = GitUtil.getRevisionId(jRepository, result.getRevision());
|
||||
RevCommit newCommit = GitUtil.getCommit(jRepository, revWalk, newCommitId);
|
||||
|
||||
String expectedFullMessage = String.format("""
|
||||
Revert "%s"
|
||||
|
||||
This reverts commit %s.""",
|
||||
revertedCommit.getShortMessage(), revertedCommit.getName());
|
||||
|
||||
assertThat(newCommit.getShortMessage()).isEqualTo(
|
||||
"Revert \"" + revertedCommit.getShortMessage() + "\"");
|
||||
assertThat(newCommit.getFullMessage()).isEqualTo(expectedFullMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSignRevertCommit() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
try (
|
||||
GitContext context = createContext();
|
||||
Repository jRepository = context.open();
|
||||
RevWalk revWalk = new RevWalk(jRepository)) {
|
||||
|
||||
ObjectId newCommitId = GitUtil.getRevisionId(jRepository, result.getRevision());
|
||||
RevCommit newCommit = GitUtil.getCommit(jRepository, revWalk, newCommitId);
|
||||
|
||||
assertThat(newCommit.getRawGpgSignature()).isNotEmpty();
|
||||
assertThat(newCommit.getRawGpgSignature()).isEqualTo(GitTestHelper.SimpleGpgSigner.getSignature());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSignNoRevertCommitIfSigningIsDisabled() throws IOException {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
request.setSign(false);
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
try (
|
||||
GitContext context = createContext();
|
||||
Repository jRepository = context.open();
|
||||
RevWalk revWalk = new RevWalk(jRepository)) {
|
||||
|
||||
ObjectId newCommitId = GitUtil.getRevisionId(jRepository, result.getRevision());
|
||||
RevCommit newCommit = GitUtil.getCommit(jRepository, revWalk, newCommitId);
|
||||
|
||||
assertThat(newCommit.getRawGpgSignature()).isNullOrEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(value = "admin", permissions = "*:*:*")
|
||||
void shouldTakeAuthorFromSubjectIfNotSet() throws IOException {
|
||||
SimplePrincipalCollection principals = new SimplePrincipalCollection();
|
||||
principals.add("admin", "AdminRealm");
|
||||
principals.add(new User("hitchhiker", "Douglas Adams", "ga@la.xy"), "AdminRealm");
|
||||
setSubject(new Subject.Builder()
|
||||
.principals(principals)
|
||||
.authenticated(true)
|
||||
.buildSubject());
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_REVISION);
|
||||
|
||||
RevertCommandResult result = command.revert(request);
|
||||
|
||||
assertThat(result.isSuccessful()).isTrue();
|
||||
|
||||
try (
|
||||
GitContext context = createContext();
|
||||
Repository jRepository = context.open();
|
||||
RevWalk revWalk = new RevWalk(jRepository)) {
|
||||
|
||||
ObjectId newCommitId = GitUtil.getRevisionId(jRepository, result.getRevision());
|
||||
RevCommit newCommit = GitUtil.getCommit(jRepository, revWalk, newCommitId);
|
||||
|
||||
PersonIdent author = newCommit.getAuthorIdent();
|
||||
assertThat(author.getName()).isEqualTo("Douglas Adams");
|
||||
assertThat(author.getEmailAddress()).isEqualTo("ga@la.xy");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowNotFoundExceptionWhenBranchNotExist() {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(HEAD_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
request.setBranch("BogusBranch");
|
||||
assertThatThrownBy(() -> command.revert(request))
|
||||
.isInstanceOf(NotFoundException.class)
|
||||
.hasMessage("could not find objectid with id BogusBranch in repository with id hitchhiker/HeartOfGold");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowNotFoundExceptionWhenRevisionNotExist() {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision("BogusRevision");
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
assertThatThrownBy(() -> command.revert(request))
|
||||
.isInstanceOf(NotFoundException.class)
|
||||
.hasMessage("could not find objectid with id BogusRevision in repository with id hitchhiker/HeartOfGold");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowNoParentExceptionWhenParentNotExist() {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(PARENTLESS_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
assertThatThrownBy(() -> command.revert(request))
|
||||
.isInstanceOf(NoParentException.class)
|
||||
.hasMessage(PARENTLESS_REVISION + " has no parent.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowMultipleParentsExceptionWhenPickingMergedCommit() {
|
||||
GitRevertCommand command = createCommand();
|
||||
RevertCommandRequest request = new RevertCommandRequest();
|
||||
request.setRevision(MERGED_REVISION);
|
||||
request.setAuthor(new Person("Luke Skywalker", "luke@je.di"));
|
||||
assertThatThrownBy(() -> command.revert(request))
|
||||
.isInstanceOf(MultipleParentsNotAllowedException.class)
|
||||
.hasMessage(MERGED_REVISION + " has more than one parent changeset, which is not allowed with this request.");
|
||||
}
|
||||
|
||||
private GitRevertCommand createCommand() {
|
||||
return new GitRevertCommand(createContext("main"), repositoryManager, gitRepositoryHookEventFactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,10 +162,10 @@ public class SimpleGitWorkingCopyFactoryTest extends AbstractGitCommandTestBase
|
||||
File workdir = createExistingClone(factory);
|
||||
|
||||
GitContext context = createContext();
|
||||
context.getGlobalConfig().setDefaultBranch("master");
|
||||
context.getGlobalConfig().setDefaultBranch("main");
|
||||
factory.reclaim(context, workdir, null);
|
||||
|
||||
assertBranchCheckedOutAndClean(workdir, "master");
|
||||
assertBranchCheckedOutAndClean(workdir, "main");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
You can properly zip a new repository with:
|
||||
|
||||
```
|
||||
ZIP_NAME=your name
|
||||
(cd scm-git-${ZIP_NAME}-test && zip -r ../scm-git-${ZIP_NAME}-test.zip .)
|
||||
```
|
||||
Binary file not shown.
Reference in New Issue
Block a user