Skip to content
Permalink
Browse files Browse the repository at this point in the history
Improve escaping of arguments when constructing Hg command calls
  • Loading branch information
chadlwilson committed Mar 4, 2022
1 parent ce02934 commit 37d3511
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 44 deletions.
Expand Up @@ -35,27 +35,28 @@
import static com.thoughtworks.go.util.command.ProcessOutputStreamConsumer.inMemoryConsumer;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.*;

public class HgCommandTest {

private static final String REVISION_0 = "b61d12de515d82d3a377ae3aae6e8abe516a2651";
private static final String REVISION_1 = "35ff2159f303ecf986b3650fc4299a6ffe5a14e1";
private static final String REVISION_2 = "ca3ebb67f527c0ad7ed26b789056823d8b9af23f";

private File serverRepo;
private File clientRepo;

private HgCommand hgCommand;

private InMemoryStreamConsumer outputStreamConsumer = inMemoryConsumer();
private File workingDirectory;
private static final String REVISION_0 = "b61d12de515d82d3a377ae3aae6e8abe516a2651";
private static final String REVISION_1 = "35ff2159f303ecf986b3650fc4299a6ffe5a14e1";
private static final String REVISION_2 = "ca3ebb67f527c0ad7ed26b789056823d8b9af23f";
private File secondBranchWorkingCopy;

@BeforeEach
public void setUp(@TempDir Path tempDir) throws IOException {
serverRepo = TempDirUtils.createTempDirectoryIn(tempDir, "testHgServerRepo").toFile();
clientRepo = TempDirUtils.createTempDirectoryIn(tempDir, "testHgClientRepo").toFile();
secondBranchWorkingCopy = TempDirUtils.createTempDirectoryIn(tempDir, "second").toFile();
clientRepo = TempDirUtils.createTempDirectoryIn(tempDir, "testHgClientRepo").toFile();
secondBranchWorkingCopy = TempDirUtils.createTempDirectoryIn(tempDir, "second").toFile();

setUpServerRepoFromHgBundle(serverRepo, new File("../common/src/test/resources/data/hgrepo.hgbundle"));
workingDirectory = new File(clientRepo.getPath());
Expand All @@ -65,11 +66,32 @@ public void setUp(@TempDir Path tempDir) throws IOException {

@Test
public void shouldCloneFromRemoteRepo() {
assertThat(clientRepo.listFiles().length > 0, is(true));
assertThat(clientRepo.listFiles().length, is(2));
}

@Test
public void shouldCloneWithEscapedRepoUrl() {
hgCommand.clone(outputStreamConsumer, new UrlArgument(echoingAliasFor("clone")));
assertNoUnescapedEcho();
}

@Test
public void shouldGetLatestModifications() throws Exception {
public void shouldCloneWithEscapedBranch() {
hgCommand = new HgCommand(null, workingDirectory, echoingAliasFor("clone"), serverRepo.getAbsolutePath(), null);
hgCommand.clone(outputStreamConsumer, new UrlArgument(serverRepo.getAbsolutePath()));
assertNoUnescapedEcho();
}

private String echoingAliasFor(String command) {
return String.format("--config=alias.%s=!echo hello world", command);
}

private void assertNoUnescapedEcho() {
assertThat(outputStreamConsumer.getAllOutput(), not(containsString("\nhello world\n")));
}

@Test
public void shouldGetLatestModifications() {
List<Modification> actual = hgCommand.latestOneModificationAsModifications();
assertThat(actual.size(), is(1));
final Modification modification = actual.get(0);
Expand All @@ -79,7 +101,7 @@ public void shouldGetLatestModifications() throws Exception {
}

@Test
public void shouldNotIncludeCommitFromAnotherBranchInGetLatestModifications() throws Exception {
public void shouldNotIncludeCommitFromAnotherBranchInGetLatestModifications() {
Modification lastCommit = hgCommand.latestOneModificationAsModifications().get(0);

makeACommitToSecondBranch();
Expand All @@ -98,7 +120,7 @@ public void shouldGetModifications() throws Exception {
}

@Test
public void shouldNotGetModificationsFromOtherBranches() throws Exception {
public void shouldNotGetModificationsFromOtherBranches() {
makeACommitToSecondBranch();
hg(workingDirectory, "pull").runOrBomb(null);

Expand Down Expand Up @@ -131,7 +153,7 @@ public void shouldUpdateToSpecificRevisionOnGivenBranch() {
}

@Test
public void shouldThrowExceptionIfUpdateFails() throws Exception {
public void shouldThrowExceptionIfUpdateFails() {
InMemoryStreamConsumer output =
ProcessOutputStreamConsumer.inMemoryConsumer();

Expand All @@ -140,7 +162,8 @@ public void shouldThrowExceptionIfUpdateFails() throws Exception {

// now hg pull will fail and throw an exception
assertThatThrownBy(() -> hgCommand.updateTo(new StringRevision("tip"), output))
.isExactlyInstanceOf(RuntimeException.class);
.isExactlyInstanceOf(RuntimeException.class)
.hasMessageContaining("Unable to update to revision [StringRevision[tip]]");
}

@Test
Expand All @@ -151,7 +174,20 @@ public void shouldGetWorkingUrl() {
}

@Test
public void shouldThrowExceptionForBadConnection() throws Exception {
public void shouldCheckConnection() {
hgCommand.checkConnection(new UrlArgument(serverRepo.getAbsolutePath()));
}

@Test
public void shouldCheckConnectionWithEscapedRepoUrl() {
assertThatThrownBy(() -> hgCommand.checkConnection(new UrlArgument(echoingAliasFor("id"))))
.isExactlyInstanceOf(CommandLineException.class)
.hasMessageContaining("repository --config")
.hasMessageContaining("not found");
}

@Test
public void shouldThrowExceptionForBadConnection() {
String url = "http://not-exists";
HgCommand hgCommand = new HgCommand(null, null, null, null, null);

Expand Down
Expand Up @@ -24,8 +24,8 @@
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -52,27 +52,31 @@ public HgCommand(String materialFingerprint, File workingDir, String branch, Str
this.secrets = secrets != null ? secrets : new ArrayList<>();
}


private boolean pull(ConsoleOutputStreamConsumer outputStreamConsumer) {
CommandLine hg = hg("pull", "-b", branch, "--config", String.format("paths.default=%s", url));
return execute(hg, outputStreamConsumer) == 0;
}

public HgVersion version() {
CommandLine hg = createCommandLine("hg").withArgs("version").withEncoding("utf-8");
CommandLine hg = createCommandLine("hg").withArgs("version").withEncoding("UTF-8");
String hgOut = execute(hg, new NamedProcessTag("hg version check")).outputAsString();
return HgVersion.parse(hgOut);
}


public int clone(ConsoleOutputStreamConsumer outputStreamConsumer, UrlArgument repositoryUrl) {
CommandLine hg = createCommandLine("hg").withArgs("clone").withArg("-b").withArg(branch).withArg(repositoryUrl)
.withArg(workingDir.getAbsolutePath()).withNonArgSecrets(secrets).withEncoding("utf-8");
CommandLine hg = createCommandLine("hg")
.withArgs("clone")
.withArg(branchArg())
.withArg("--")
.withArg(repositoryUrl)
.withArg(workingDir.getAbsolutePath())
.withNonArgSecrets(secrets)
.withEncoding("UTF-8");
return execute(hg, outputStreamConsumer);
}

public void checkConnection(UrlArgument repositoryURL) {
execute(createCommandLine("hg").withArgs("id", "--id").withArg(repositoryURL).withNonArgSecrets(secrets).withEncoding("utf-8"), new NamedProcessTag(repositoryURL.forDisplay()));
CommandLine hg = createCommandLine("hg")
.withArgs("id", "--id", "--")
.withArg(repositoryURL)
.withNonArgSecrets(secrets)
.withEncoding("UTF-8");
execute(hg, new NamedProcessTag(repositoryURL.forDisplay()));
}

public void updateTo(Revision revision, ConsoleOutputStreamConsumer outputStreamConsumer) {
Expand All @@ -81,6 +85,11 @@ public void updateTo(Revision revision, ConsoleOutputStreamConsumer outputStream
}
}

private boolean pull(ConsoleOutputStreamConsumer outputStreamConsumer) {
CommandLine hg = hg("pull", branchArg(), "--config", String.format("paths.default=%s", url));
return execute(hg, outputStreamConsumer) == 0;
}

private boolean update(Revision revision, ConsoleOutputStreamConsumer outputStreamConsumer) {
CommandLine hg = hg("update", "--clean", "-r", revision.getRevision());
return execute(hg, outputStreamConsumer) == 0;
Expand All @@ -105,36 +114,29 @@ public List<Modification> latestOneModificationAsModifications() {
return findRecentModifications(1);
}

private String templatePath() {
if (templatePath == null) {
String file = HgCommand.class.getResource("/hg.template").getFile();
try {
templatePath = URLDecoder.decode(new File(file).getAbsolutePath(), "UTF-8");
} catch (UnsupportedEncodingException e) {
templatePath = URLDecoder.decode(new File(file).getAbsolutePath());
}
}
return templatePath;
}

List<Modification> findRecentModifications(int count) {
private List<Modification> findRecentModifications(int count) {
// Currently impossible to check modifications on a remote repository.
InMemoryStreamConsumer consumer = inMemoryConsumer();
bombUnless(pull(consumer), "Failed to run hg pull command: " + consumer.getAllOutput());
CommandLine hg = hg("log", "--limit", String.valueOf(count), "-b", branch, "--style", templatePath());
CommandLine hg = hg("log", "--limit", String.valueOf(count), branchArg(), "--style", templatePath());
return new HgModificationSplitter(execute(hg)).modifications();
}

public List<Modification> modificationsSince(Revision revision) {
InMemoryStreamConsumer consumer = inMemoryConsumer();
bombUnless(pull(consumer), "Failed to run hg pull command: " + consumer.getAllOutput());
CommandLine hg = hg("log",
"-r", "tip:" + revision.getRevision(),
"-b", branch,
"--style", templatePath());
CommandLine hg = hg("log", "-r", "tip:" + revision.getRevision(), branchArg(), "--style", templatePath());
return new HgModificationSplitter(execute(hg)).filterOutRevision(revision);
}

private String templatePath() {
if (templatePath == null) {
String file = HgCommand.class.getResource("/hg.template").getFile();
templatePath = URLDecoder.decode(new File(file).getAbsolutePath(), StandardCharsets.UTF_8);
}
return templatePath;
}

public ConsoleResult workingRepositoryUrl() {
CommandLine hg = hg("showconfig", "paths.default");

Expand All @@ -144,6 +146,10 @@ public ConsoleResult workingRepositoryUrl() {
return result;
}

private String branchArg() {
return "--branch=" + branch;
}

private CommandLine hg(String... arguments) {
return createCommandLine("hg").withArgs(arguments).withNonArgSecrets(secrets).withWorkingDir(workingDir).withEncoding("UTF-8");
}
Expand Down

0 comments on commit 37d3511

Please sign in to comment.