Mercurial > hg > mercurial
changeset 1116:7b244aaead2e TW-83599
TW-83599: recognize "path contains illegal component" error and retry pull command with explicit revision
line wrap: on
line diff
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java Thu Sep 14 17:09:37 2023 +0200 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java Mon Oct 09 17:48:08 2023 +0200 @@ -242,6 +242,7 @@ checkMergeWithWorkDirAncestor(stderr); checkNothingToMerge(stderr); checkUnknownException(stderr); + checkPathIllegalComponent(stderr); } private void checkUnrelatedRepository(@NotNull final String stderr) throws UnrelatedRepositoryException { @@ -298,6 +299,16 @@ throw new UnknownMercurialException(stderr); } + private void checkPathIllegalComponent(@NotNull final String stderr) throws PathIllegalComponentException { + final String prefix = "abort: path contains illegal component: "; + int idx = stderr.indexOf(prefix); + if (idx != -1) { + int startIdx = idx + prefix.length(); + final String component = stderr.substring(startIdx); + throw new PathIllegalComponentException(component); + } + } + private static Set<Integer> setOf(Integer... ints) { return new HashSet<>(asList(ints)); }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java Thu Sep 14 17:09:37 2023 +0200 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java Mon Oct 09 17:48:08 2023 +0200 @@ -31,6 +31,8 @@ private int myTimeout; private boolean myTraceback; private boolean myProfile; + private ChangeSet myChangeSet; + private String myNamedRevision; public PullCommand(@NotNull CommandSettings commandSettings, @NotNull String hgPath, @@ -64,6 +66,16 @@ return this; } + public PullCommand withChangeSet(@NotNull ChangeSet changeSet) { + myChangeSet = changeSet; + return this; + } + + public PullCommand withNamedRevision(@NotNull String namedRevision) { + myNamedRevision = namedRevision; + return this; + } + public void call() throws VcsException { removeLocks(); @@ -84,6 +96,12 @@ cli.setHasProgress(true); } + if (myChangeSet != null) { + cli.addParameters("--rev", myChangeSet.getId()); + } else if (myNamedRevision != null) { + cli.addParameters("--rev", myNamedRevision); + } + String pullUrl = myAuthSettings.getRepositoryUrlWithCredentials(myPullUrl); cli.addParameter(pullUrl); runCommand(cli, settings);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/exception/PathIllegalComponentException.java Mon Oct 09 17:48:08 2023 +0200 @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2023 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception; + +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +public class PathIllegalComponentException extends VcsException { + + @NotNull private final String myComponent; + + public PathIllegalComponentException(@NotNull String component) { + super("Path contains illegal component: " + component); + myComponent = component; + } + + @NotNull + public String getComponent() { + return myComponent; + } +}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java Thu Sep 14 17:09:37 2023 +0200 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java Mon Oct 09 17:48:08 2023 +0200 @@ -19,6 +19,7 @@ import jetbrains.buildServer.buildTriggers.vcs.AbstractVcsPropertiesProcessor; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.AbandonedTransactionFound; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.PathIllegalComponentException; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnrelatedRepositoryException; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.WrongSubrepoUrlException; import jetbrains.buildServer.log.Loggers; @@ -539,8 +540,12 @@ } } + private void syncRepository(@NotNull final HgVcsRoot root, @NotNull final ChangeSet cset) throws VcsException { + syncRepository(root, cset, false); + } + /* clone the repo if it doesn't exist, pull the repo if it doesn't contain specified changeSet */ - private void syncRepository(@NotNull final HgVcsRoot root, @NotNull final ChangeSet cset) throws VcsException { + private void syncRepository(@NotNull final HgVcsRoot root, @NotNull final ChangeSet cset, boolean explicitRevision) throws VcsException { File workingDir = getWorkingDir(root); lockWorkDir(workingDir); HgRepo repo = createRepo(root); @@ -552,14 +557,27 @@ repo.setDefaultPath(root.getRepository()); repo.setTeamCityConfig(root.getCustomHgConfigServer()); try { - repo.pull().fromRepository(root.getRepository()) + final PullCommand pullCommand = repo.pull().fromRepository(root.getRepository()) .withTimeout(myConfig.getPullTimeout()) - .withProfile(myConfig.runWithProfile(root)) - .call(); + .withProfile(myConfig.runWithProfile(root)); + if (explicitRevision) { + pullCommand.withChangeSet(cset); + } + pullCommand.call(); } catch (UnrelatedRepositoryException e) { Loggers.VCS.warn("Repository at " + root.getRepository() + " is unrelated, clone it again"); myMirrorManager.forgetDir(workingDir); syncRepository(root, cset); + } catch (PathIllegalComponentException e) { + final StringBuilder logMessage = new StringBuilder("Pulling from repository at ").append(root.getRepository()) + .append(" failed because of illegal path component ").append(e.getComponent()).append("."); + if (myConfig.retryPullForIllegalPathComponent() && !explicitRevision) { + logMessage.append(" Retrying with explicit pull for revision ").append(cset).append("."); + Loggers.VCS.warn(logMessage.toString()); + syncRepository(root, cset, true); + return; + } + Loggers.VCS.warn(logMessage.toString()); } } finally { unlockWorkDir(workingDir); @@ -581,11 +599,12 @@ public <T> T syncRepository(@NotNull HgVcsRoot root, @NotNull SyncSettings<T> settings, @Nullable OperationContext context) throws VcsException { boolean customWorkingDir = root.getCustomWorkingDir() != null; File workingDir = getWorkingDir(root); + boolean pullFromTip = false; int attemptsLeft = 3; VcsException lastError = null; while (attemptsLeft-- > 0) { try { - return syncRepositoryOnce(root, settings, workingDir, context); + return syncRepositoryOnce(root, settings, workingDir, context, pullFromTip); } catch (UnrelatedRepositoryException e) { if (customWorkingDir) throw new VcsException(e.getMessage() + ". VCS root uses a custom clone dir, manual recovery is required.", e); @@ -600,13 +619,26 @@ + workingDir.getAbsolutePath() + ". Clone it again, attempts left " + attemptsLeft); myMirrorManager.forgetDir(workingDir); lastError = e; + } catch (PathIllegalComponentException e) { + final StringBuilder logMessage = new StringBuilder("Pull failed because of illegal path component ").append(e.getComponent()) + .append(" for repository ").append(root.getRepository()).append(" at ").append(workingDir.getAbsolutePath()).append("."); + if (myConfig.retryPullForIllegalPathComponent()) { + logMessage.append(" Retrying with pull from tip revision, attempts left ").append(attemptsLeft); + pullFromTip = true; + } + Loggers.VCS.warn(logMessage.toString()); + lastError = e; } } throw lastError; } - private <T> T syncRepositoryOnce(@NotNull HgVcsRoot root, @NotNull SyncSettings<T> settings, @NotNull File workingDir, @Nullable OperationContext context) throws VcsException { + private <T> T syncRepositoryOnce(@NotNull HgVcsRoot root, + @NotNull SyncSettings<T> settings, + @NotNull File workingDir, + @Nullable OperationContext context, + boolean fromTip) throws VcsException { lockWorkDir(workingDir, settings.getProgressConsumer()); HgRepo repo = context != null ? context.createRepo(root) : createRepo(root); try { @@ -615,10 +647,13 @@ repo.setDefaultPath(root.getRepository()); repo.setTeamCityConfig(root.getCustomHgConfigServer()); resetBookmarks(repo); - repo.pull().fromRepository(root.getRepository()) + final PullCommand pullCommand = repo.pull().fromRepository(root.getRepository()) .withTimeout(myConfig.getPullTimeout()) - .withProfile(myConfig.runWithProfile(root)) - .call(); + .withProfile(myConfig.runWithProfile(root)); + if (fromTip) { + pullCommand.withNamedRevision("tip"); + } + pullCommand.call(); return settings.getCmd().call(); } finally { unlockWorkDir(workingDir);
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java Thu Sep 14 17:09:37 2023 +0200 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java Mon Oct 09 17:48:08 2023 +0200 @@ -61,4 +61,6 @@ boolean runWithProfile(@NotNull HgVcsRoot root); boolean computeFromRevisions(); + + boolean retryPullForIllegalPathComponent(); }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java Thu Sep 14 17:09:37 2023 +0200 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java Mon Oct 09 17:48:08 2023 +0200 @@ -133,4 +133,8 @@ public boolean computeFromRevisions() { return TeamCityProperties.getBooleanOrTrue("teamcity.hg.computeFromRevisions"); } + + public boolean retryPullForIllegalPathComponent() { + return TeamCityProperties.getBooleanOrTrue("teamcity.hg.retryPullForIllegalPathComponent"); + } }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialSupportBuilder.java Thu Sep 14 17:09:37 2023 +0200 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialSupportBuilder.java Mon Oct 09 17:48:08 2023 +0200 @@ -68,7 +68,6 @@ return vcs; } - public MercurialSupportBuilder withConfig(@NotNull ServerPluginConfig config) { myConfig = config; return this;
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java Thu Sep 14 17:09:37 2023 +0200 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java Mon Oct 09 17:48:08 2023 +0200 @@ -17,6 +17,7 @@ import com.intellij.openapi.util.SystemInfo; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.PathIllegalComponentException; import jetbrains.buildServer.util.FileUtil; import jetbrains.buildServer.util.TestFor; import jetbrains.buildServer.vcs.*; @@ -809,6 +810,26 @@ CheckoutRules.DEFAULT); } + @TestFor(issues = "TW-83599") + public void should_retry_pull_on_illegal_path_component() throws Exception { + final VcsRootImpl vcsRoot = createVcsRoot(simpleRepo()); + // we can't realiably force illegal path component errors with real hg repos + final RepoFactory failOnceRepoFactory = failOnceRepoFactory(new PathIllegalComponentException("some-component")); + myVcs = mercurialSupport().withConfig(myPluginConfig).withRepoFactory(failOnceRepoFactory).build(); + + myVcs.syncRepository(vcsRoot); + } + + @TestFor(issues = "TW-83599") + public void get_content_should_retry_pull_on_illegal_path_component() throws IOException, VcsException { + VcsRootImpl vcsRoot = createVcsRoot(simpleRepo()); + // we can't realiably force illegal path component errors with real hg repos + final RepoFactory failOnceRepoFactory = failOnceRepoFactory(new PathIllegalComponentException("some-component")); + myVcs = mercurialSupport().withConfig(myPluginConfig).withRepoFactory(failOnceRepoFactory).build(); + + byte[] content = myVcs.getContent("dir1/subdir/file2.txt", vcsRoot, "4:b06a290a363b"); + assertEquals(new String(content), "bbb"); + } private void assertFiles(final List<String> expectedFiles, final ModificationData modificationData) { Set<String> actualFiles = new HashSet<>(); @@ -838,5 +859,36 @@ public void test_collect_changes_using_checkout_rules() { assertTrue(myVcs.getCollectChangesPolicy() instanceof CollectChangesByCheckoutRules); } + + @NotNull + private RepoFactory failOnceRepoFactory(@NotNull VcsException failure) throws IOException { + final CommandSettingsForRootImpl commandSettingsFactory = new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()); + + return new RepoFactory(myPluginConfig, commandSettingsFactory, myHgPathProvider) { + private boolean myDidFail = false; + + @NotNull + @Override + public ServerHgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException { + if (myDidFail) { + return super.createRepo(root, workingDir); + } else { + myDidFail = true; + final CommandSettingsFactory rootSettingsFactory = commandSettingsFactory.forRoot(root); + return new ServerHgRepo(rootSettingsFactory, myConfig, workingDir, myHgPathProvider.getHgPath(root), root.getAuthSettings()) { + @Override + public PullCommand pull() { + return new PullCommand(rootSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings) { + @Override + public void call() throws VcsException { + throw failure; + } + }; + } + }; + } + } + }; + } }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigBuilder.java Thu Sep 14 17:09:37 2023 +0200 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigBuilder.java Mon Oct 09 17:48:08 2023 +0200 @@ -115,6 +115,10 @@ public boolean computeFromRevisions() { return true; } + + public boolean retryPullForIllegalPathComponent() { + return true; + } }; }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResultTest.java Thu Sep 14 17:09:37 2023 +0200 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResultTest.java Mon Oct 09 17:48:08 2023 +0200 @@ -21,6 +21,7 @@ import jetbrains.buildServer.ExecResult; import jetbrains.buildServer.StreamGobbler; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.AbandonedTransactionFound; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.PathIllegalComponentException; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnknownRevisionException; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnrelatedRepositoryException; import jetbrains.buildServer.util.TestFor; @@ -136,6 +137,16 @@ } } + public void should_detect_illegal_path_component() throws VcsException { + final CommandResult commandResult = commandResultFor(execResult().withStderr("abort: path contains illegal component: /_mercurial_classpath_template.java.i")); + try { + commandResult.checkCommandFailed(); + fail("expected PathIllegalComponentException to be thrown"); + } catch (PathIllegalComponentException e) { + assertEquals("/_mercurial_classpath_template.java.i", e.getComponent()); + } + } + public void exception_should_not_contain_command_stdout_or_stderr() { final String stdout = "300Mb of output"; final String stderr = "300Mb from stderr";