diff --git a/gradle/changelog/added_force_push.yaml b/gradle/changelog/added_force_push.yaml new file mode 100644 index 0000000000..4000ebb242 --- /dev/null +++ b/gradle/changelog/added_force_push.yaml @@ -0,0 +1,2 @@ +- type: added + description: Force option to internal push command api diff --git a/scm-core/src/main/java/sonia/scm/repository/Feature.java b/scm-core/src/main/java/sonia/scm/repository/Feature.java index 31863523aa..fa34364752 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Feature.java +++ b/scm-core/src/main/java/sonia/scm/repository/Feature.java @@ -48,5 +48,11 @@ public enum Feature * * @since 2.23.0 */ - MODIFICATIONS_BETWEEN_REVISIONS + MODIFICATIONS_BETWEEN_REVISIONS, + /** + * The repository supports pushing with a force flag. + * + * @since 2.47.0 + */ + FORCE_PUSH } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java index 107577460b..ceefec206f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java @@ -28,6 +28,8 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.FeatureNotSupportedException; +import sonia.scm.repository.Feature; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.PushCommand; @@ -35,6 +37,7 @@ import sonia.scm.repository.spi.PushCommandRequest; import java.io.IOException; import java.net.URL; +import java.util.Set; /** * The push command push changes to a other repository. @@ -48,9 +51,11 @@ public final class PushCommandBuilder { private final PushCommand command; private final PushCommandRequest request = new PushCommandRequest(); + private final Set supportedFeatures; - PushCommandBuilder(PushCommand command) { + PushCommandBuilder(PushCommand command, Set supportedFeatures) { this.command = command; + this.supportedFeatures = supportedFeatures; } /** @@ -67,12 +72,22 @@ public final class PushCommandBuilder { return this; } + public PushCommandBuilder withForce(boolean force) { + if (!supportedFeatures.contains(Feature.FORCE_PUSH)) { + throw new FeatureNotSupportedException(Feature.FORCE_PUSH.name()); + } + + request.setForce(force); + return this; + } + /** * Push all changes to the given remote repository. * * @param remoteRepository remote repository * @return informations of the executed push command * @throws IOException + * @throws PushFailedException when the push (maybe just partially) failed (since 2.47.0) */ public PushResponse push(Repository remoteRepository) throws IOException { Subject subject = SecurityUtils.getSubject(); @@ -95,6 +110,7 @@ public final class PushCommandBuilder { * @param url url of a remote repository * @return informations of the executed push command * @throws IOException + * @throws PushFailedException when the push (maybe just partially) failed (since 2.47.0) * @since 1.43 */ public PushResponse push(String url) throws IOException { diff --git a/scm-core/src/main/java/sonia/scm/repository/api/PushFailedException.java b/scm-core/src/main/java/sonia/scm/repository/api/PushFailedException.java new file mode 100644 index 0000000000..54946f711e --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/PushFailedException.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.api; + +import sonia.scm.ContextEntry; +import sonia.scm.ExceptionWithContext; +import sonia.scm.repository.Repository; + +public class PushFailedException extends ExceptionWithContext { + + public static final String CODE = "dnWjIroRhT"; + + public PushFailedException(Repository repository) { + super(ContextEntry.ContextBuilder.entity(repository).build(), "Failed to push"); + } + + @Override + public String getCode() { + return CODE; + } +} 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 64ac0e541a..ca10dae16e 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 @@ -346,7 +346,7 @@ public final class RepositoryService implements Closeable { public PushCommandBuilder getPushCommand() { LOG.debug("create push command for repository {}", repository); - return new PushCommandBuilder(provider.getPushCommand()); + return new PushCommandBuilder(provider.getPushCommand(), provider.getSupportedFeatures()); } /** diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/PushCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/PushCommandRequest.java index 2607a693c6..7574c57137 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/PushCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/PushCommandRequest.java @@ -24,10 +24,23 @@ package sonia.scm.repository.spi; +import lombok.Getter; +import lombok.Setter; + /** * Request object for {@link PushCommand}. * * @author Sebastian Sdorra * @since 1.31 */ -public final class PushCommandRequest extends RemoteCommandRequest {} +@Getter +@Setter +public final class PushCommandRequest extends RemoteCommandRequest { + private boolean force = false; + + @Override + public void reset() { + force = false; + super.reset(); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java index 09f3f03378..3004eb7500 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java @@ -32,19 +32,26 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RemoteRefUpdate; -import org.eclipse.jgit.transport.ScmTransportProtocol; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.api.PushFailedException; import java.io.File; import java.util.Collection; +import java.util.List; public abstract class AbstractGitPushOrPullCommand extends AbstractGitCommand { + private static final List REJECTED_STATUSES = List.of( + RemoteRefUpdate.Status.REJECTED_NODELETE, + RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD, + RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, + RemoteRefUpdate.Status.REJECTED_OTHER_REASON + ); private static final Logger LOG = LoggerFactory.getLogger(AbstractGitPushOrPullCommand.class); protected GitRepositoryHandler handler; @@ -54,7 +61,7 @@ public abstract class AbstractGitPushOrPullCommand extends AbstractGitCommand { this.handler = handler; } - protected long push(Repository source, String remoteUrl, String username, String password) { + protected long push(Repository source, String remoteUrl, String username, String password, boolean force) { Git git = Git.wrap(source); org.eclipse.jgit.api.PushCommand push = git.push(); @@ -63,19 +70,23 @@ public abstract class AbstractGitPushOrPullCommand extends AbstractGitCommand { } push.setPushAll().setPushTags(); push.setRemote(remoteUrl); + push.setForce(force); - long counter = -1; + long counter; try { Iterable results = push.call(); - - if (results != null) { - counter = 0; - - for (PushResult result : results) { - counter += count(git, result); - } + if (hasPushFailed(results)) { + throw new PushFailedException(repository); } + + counter = 0; + + for (PushResult result : results) { + counter += count(git, result); + } + } catch (PushFailedException ex) { + throw ex; } catch (Exception ex) { throw new InternalRepositoryException(repository, "could not execute push/pull command", ex); } @@ -83,6 +94,22 @@ public abstract class AbstractGitPushOrPullCommand extends AbstractGitCommand { return counter; } + private boolean hasPushFailed(Iterable pushResults) { + if (pushResults == null) { + return true; + } + + for (PushResult nextResult : pushResults) { + for(RemoteRefUpdate remoteUpdate : nextResult.getRemoteUpdates()) { + if(REJECTED_STATUSES.contains(remoteUpdate.getStatus())) { + return true; + } + } + } + + return false; + } + protected String getRemoteUrl(RemoteCommandRequest request) { String url; sonia.scm.repository.Repository remRepo = request.getRemoteRepository(); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java index 8b062badd6..c690838711 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java @@ -166,7 +166,7 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand try (Git git = Git.open(sourceDirectory)) { source = git.getRepository(); - response = new PullResponse(push(source, getRemoteUrl(targetDirectory), username, password)); + response = new PullResponse(push(source, getRemoteUrl(targetDirectory), username, password, false)); } finally { GitUtil.close(source); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java index 11003cf5dd..8ddfec6444 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java @@ -49,6 +49,6 @@ public class GitPushCommand extends AbstractGitPushOrPullCommand implements Push LOG.debug("push changes from {} to {}", repository, remoteUrl); - return new PushResponse(push(open(), remoteUrl, request.getUsername(), request.getPassword())); + return new PushResponse(push(open(), remoteUrl, request.getUsername(), request.getPassword(), request.isForce())); } } 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 2a4cbd1023..4f7390270c 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 @@ -65,7 +65,8 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider { protected static final Set FEATURES = EnumSet.of( Feature.INCOMING_REVISION, - Feature.MODIFICATIONS_BETWEEN_REVISIONS + Feature.MODIFICATIONS_BETWEEN_REVISIONS, + Feature.FORCE_PUSH ); private final GitContext context; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java index 2424e1365b..0fb91c5fe8 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitPushCommandTest.java @@ -30,11 +30,14 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Test; import sonia.scm.repository.GitConfig; +import sonia.scm.repository.api.PushFailedException; import sonia.scm.repository.api.PushResponse; import java.io.IOException; import java.util.Iterator; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -82,6 +85,51 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase assertEquals(o1, commits.next()); } + @Test + public void testForcePush() throws IOException, GitAPIException { + write(outgoing, outgoingDirectory, "a.txt", "content of a.txt"); + RevCommit outgoingCommit = commit(outgoing, "added a"); + + write(incoming, incomingDirectory, "a.txt", "conflicting change of a.txt"); + commit(incoming, "changed a"); + + PushCommandRequest request = new PushCommandRequest(); + request.setRemoteRepository(incomingRepository); + request.setForce(true); + + GitPushCommand cmd = createCommand(); + PushResponse response = cmd.push(request); + + assertNotNull(response); + assertEquals(0L, response.getChangesetCount()); + + Iterator commits = incoming.log().call().iterator(); + + assertEquals(outgoingCommit, commits.next()); + assertThat(commits.hasNext()).isFalse(); + } + + @Test + public void testFailedPushBecauseOfConflict() throws IOException, GitAPIException { + write(outgoing, outgoingDirectory, "a.txt", "content of a.txt"); + commit(outgoing, "added a"); + + write(incoming, incomingDirectory, "a.txt", "conflicting change of a.txt"); + RevCommit incomingCommit = commit(incoming, "changed a"); + + GitPushCommand cmd = createCommand(); + PushCommandRequest request = new PushCommandRequest(); + request.setRemoteRepository(incomingRepository); + + assertThatThrownBy(() -> cmd.push(request)) + .isInstanceOf(PushFailedException.class) + .hasMessageContaining("Failed to push"); + + Iterator commits = incoming.log().call().iterator(); + assertEquals(incomingCommit, commits.next()); + assertThat(commits.hasNext()).isFalse(); + } + /** * Method description * diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java index 5aec9adad3..d868da1752 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java @@ -26,25 +26,22 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Strings; import org.javahg.Changeset; import org.javahg.commands.ExecutionException; -import com.google.common.base.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.api.ImportFailedException; +import sonia.scm.repository.api.PushFailedException; import sonia.scm.repository.api.PushResponse; import javax.inject.Inject; import java.io.IOException; import java.util.List; -import static sonia.scm.ContextEntry.ContextBuilder.entity; - //~--- JDK imports ------------------------------------------------------------ /** - * * @author Sebastian Sdorra */ public class HgPushCommand extends AbstractHgPushOrPullCommand implements PushCommand { @@ -74,9 +71,17 @@ public class HgPushCommand extends AbstractHgPushOrPullCommand implements PushCo List result; try { - result = org.javahg.commands.PushCommand.on(open()).execute(url); + result = builder.call(() -> { + org.javahg.commands.PushCommand hgPush = org.javahg.commands.PushCommand.on(open()); + + if (request.isForce()) { + hgPush.force(); + } + + return hgPush.execute(url); + }); } catch (ExecutionException ex) { - throw new ImportFailedException(entity(getRepository()).build(), "could not execute pull command", ex); + throw new PushFailedException(getRepository()); } return new PushResponse(result.size()); 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 4a75db7557..a615f318c0 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 @@ -64,7 +64,8 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider { public static final Set FEATURES = EnumSet.of( Feature.COMBINED_DEFAULT_BRANCH, Feature.MODIFICATIONS_BETWEEN_REVISIONS, - Feature.INCOMING_REVISION + Feature.INCOMING_REVISION, + Feature.FORCE_PUSH ); private final Injector commandInjector; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgPushCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgPushCommandTest.java new file mode 100644 index 0000000000..4c1a7c6574 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgPushCommandTest.java @@ -0,0 +1,110 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.spi; + +import org.javahg.Changeset; +import org.junit.Test; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.net.GlobalProxyConfiguration; +import sonia.scm.repository.HgConfigResolver; +import sonia.scm.repository.HgTestUtil; +import sonia.scm.repository.api.PushFailedException; +import sonia.scm.repository.api.PushResponse; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class HgPushCommandTest extends IncomingOutgoingTestBase { + + @Test + public void testPush() throws IOException { + writeNewFile(outgoing, outgoingDirectory, "a.txt", "content of a.txt"); + Changeset o1 = commit(outgoing, "added a"); + + writeNewFile(outgoing, outgoingDirectory, "b.txt", "content of b.txt"); + Changeset o2 = commit(outgoing, "added b"); + + HgPushCommand cmd = createPushCommand(); + PushCommandRequest request = new PushCommandRequest(); + request.setRemoteRepository(incomingRepository); + + PushResponse response = cmd.push(request); + assertThat(response).isNotNull(); + assertThat(response.getChangesetCount()).isEqualTo(2); + + assertThat(incoming.changeset(o1.getNode())).isNotNull(); + assertThat(incoming.changeset(o2.getNode())).isNotNull(); + } + + @Test + public void testForcePush() throws IOException { + writeNewFile(outgoing, outgoingDirectory, "a.txt", "content of a.txt"); + Changeset outgoingCommit = commit(outgoing, "added a"); + + writeNewFile(incoming, incomingDirectory, "a.txt", "conflicting change of a.txt"); + commit(incoming, "changed a"); + + HgPushCommand cmd = createPushCommand(); + PushCommandRequest request = new PushCommandRequest(); + request.setRemoteRepository(incomingRepository); + request.setForce(true); + + PushResponse response = cmd.push(request); + assertThat(response).isNotNull(); + assertThat(response.getChangesetCount()).isEqualTo(1); + + assertThat(incoming.tip().getNode()).isEqualTo(outgoingCommit.getNode()); + } + + @Test + public void testFailedPushBecauseOfConflict() throws IOException { + writeNewFile(outgoing, outgoingDirectory, "a.txt", "content of a.txt"); + commit(outgoing, "added a"); + + writeNewFile(incoming, incomingDirectory, "a.txt", "conflicting change of a.txt"); + Changeset incomingCommit = commit(incoming, "changed a"); + + HgPushCommand cmd = createPushCommand(); + PushCommandRequest request = new PushCommandRequest(); + request.setRemoteRepository(incomingRepository); + + assertThatThrownBy(() -> cmd.push(request)) + .isInstanceOf(PushFailedException.class) + .hasMessageContaining("Failed to push"); + + assertThat(incoming.tip().getNode()).isEqualTo(incomingCommit.getNode()); + } + + private HgPushCommand createPushCommand() { + HgConfigResolver resolver = new HgConfigResolver(handler); + return new HgPushCommand( + handler, + new HgCommandContext(resolver, HgTestUtil.createFactory(handler, outgoingDirectory), outgoingRepository), + new TemporaryConfigFactory(new GlobalProxyConfiguration(new ScmConfiguration())) + ); + } +} diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json index f0a0c919c8..2f55979e06 100644 --- a/scm-webapp/src/main/resources/locales/de/plugins.json +++ b/scm-webapp/src/main/resources/locales/de/plugins.json @@ -197,6 +197,10 @@ "transactionId": "Transaktions-ID", "moreInfo": "Für mehr Informationen, siehe", "violations": "Ungültige Werte:", + "dnWjIroRhT": { + "displayName": "Push fehlgeschlagen", + "description": "Das Pushen des Repositorys ist fehlgeschlagen" + }, "AGR7UzkhA1": { "displayName": "Nicht gefunden", "description": "Der gewünschte Datensatz konnte nicht gefunden werden. Möglicherweise wurde er in einer weiteren Session gelöscht." diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json index fe5a3feda8..fdd1082472 100644 --- a/scm-webapp/src/main/resources/locales/en/plugins.json +++ b/scm-webapp/src/main/resources/locales/en/plugins.json @@ -197,6 +197,10 @@ "transactionId": "Transaction ID", "moreInfo": "For more information, see", "violations": "Violations:", + "dnWjIroRhT": { + "displayName": "Push failed", + "description": "Pushing the repository has failed" + }, "AGR7UzkhA1": { "displayName": "Not found", "description": "The requested entity could not be found. It may have been deleted in another session."