package org.paylogic.jenkins.advancedscm.backends.helpers; import hudson.AbortException; import hudson.FilePath; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.TaskListener; import hudson.plugins.mercurial.HgExe; import hudson.plugins.mercurial.MercurialSCM; import hudson.util.ArgumentListBuilder; import lombok.Getter; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import javax.annotation.CheckForNull; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.concurrent.TimeUnit; public class AdvancedHgExe extends HgExe { @Getter private FilePath filePath; public static int DEFAULT_TIMEOUT = 6 * 60; // 6 minutes (time is in seconds) public static int DEFAULT_PUSH_TIMEOUT = 60 * 60 * 60; // one hour (time is in seconds) public AdvancedHgExe(MercurialSCM scm, Launcher launcher, AbstractBuild build, TaskListener listener) throws IOException, InterruptedException { super(scm, launcher, build, listener); FilePath path = build.getWorkspace(); if (scm.getSubdir() != null && !scm.getSubdir().isEmpty()) { path = path.child(scm.getSubdir()); } this.filePath = path; } /** * Runs the command and captures the output. */ public String popen(FilePath repository, TaskListener listener, int timeout, ArgumentListBuilder args, int[] returnCodes) throws IOException, InterruptedException { args = seed(false).add(args.toCommandArray()); ByteArrayOutputStream data = new ByteArrayOutputStream(); if (ArrayUtils.contains(returnCodes, joinWithPossibleTimeout(launch(args).pwd(repository).stdout(data), timeout, listener))) { return data.toString(); } else { // We override this because we don't want sensitive data in error logs. String command = ""; for (String arg: args.toList()) { if (!arg.contains("auth") || !arg.contains("ssh")) { command += arg; } else { command += "********"; } command += " "; } listener.error("Failed to run " + command); listener.getLogger().write(data.toByteArray()); throw new AbortException(data.toString()); } } /** * For use with {@link #launch} (or similar) when running commands not inside a build and which therefore might not be easily killed. */ public static int joinWithPossibleTimeout(Launcher.ProcStarter proc, int timeout, final TaskListener listener) throws IOException, InterruptedException { return timeout != 0 ? proc.start().joinWithTimeout(timeout, TimeUnit.SECONDS, listener) : proc.start().joinWithTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS, listener); } public String popen(FilePath repository, TaskListener listener, int timeout, ArgumentListBuilder args) throws IOException, InterruptedException { int[] returnCodes = {0}; return popen(repository, listener, timeout, args, returnCodes); } public @CheckForNull String branch(String... extraArgs) throws IOException, InterruptedException { ArgumentListBuilder builder = new ArgumentListBuilder("branch"); for(String item : extraArgs){ builder.add(item); } String output = popen(this.filePath, listener, 0, builder); listener.getLogger().append(output); return output; } public @CheckForNull String branches(String[] extraArgs) throws IOException, InterruptedException { ArgumentListBuilder builder = new ArgumentListBuilder("branches"); for(String item : extraArgs){ builder.add(item); }String output = popen(this.filePath, listener, 0, builder); return output; } public String update(String revision) throws IOException, InterruptedException { String output = popen(this.filePath, listener, 0, new ArgumentListBuilder("update", revision)); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } public String updateClean(String revision) throws IOException, InterruptedException { String output = popen(this.filePath, listener, 0, new ArgumentListBuilder("update", "-C", revision)); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } public String clean() throws IOException, InterruptedException { String output = popen(this.filePath, listener, 0, new ArgumentListBuilder( "--config", "extensions.purge=", "purge", "--all")); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } private static final String[] EMPTY = {}; public String[] out() throws IOException, InterruptedException { int [] returnCodes = {0, 1}; String output = popen(this.filePath, listener, 0, new ArgumentListBuilder( "out", "-q", "--template", "{rev}:"), returnCodes); if (StringUtils.isEmpty(output)) { return EMPTY; } return output.split(":"); } public String commit(String message, String username, String... extraArgs) throws IOException, InterruptedException { int [] returnCodes = {0, 1}; ArgumentListBuilder builder = new ArgumentListBuilder( "--config", "ui.username=" + username, "commit", "-m", message); for(String item : extraArgs){ builder.add(item); } String output = popen( this.filePath, listener, 0, builder, returnCodes); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } public String merge(String revision) throws IOException, InterruptedException { int [] returnCodes = {0, 255}; String output = popen(this.filePath, listener, 0, new ArgumentListBuilder("merge", "--tool", "internal:merge", revision), returnCodes); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } public String push(String[] extraArgs) throws IOException, InterruptedException { ArgumentListBuilder builder = new ArgumentListBuilder("push", "--new-branch"); for(String item : extraArgs){ builder.add("-b", item); } String output = popen(this.filePath, listener, DEFAULT_PUSH_TIMEOUT, builder); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } public String strip(String[] extraArgs) throws IOException, InterruptedException { ArgumentListBuilder builder = new ArgumentListBuilder("--config", "extensions.strip=", "strip"); for(String item : extraArgs){ builder.add(item.trim()); } String output = popen(this.filePath, listener, 0, builder); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } public String pullChanges() throws IOException, InterruptedException { // This has a wheird name because of extended class. String output = popen(this.filePath, listener, DEFAULT_PUSH_TIMEOUT, new ArgumentListBuilder("pull")); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } public String pullChanges(String otherRepo) throws IOException, InterruptedException { String output = popen(this.filePath, listener, DEFAULT_PUSH_TIMEOUT, new ArgumentListBuilder("pull", otherRepo)); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } public String pullChanges(String otherRepo, String branch) throws IOException, InterruptedException { String output = popen(this.filePath, listener, DEFAULT_PUSH_TIMEOUT, new ArgumentListBuilder( "pull", otherRepo, "-r", branch)); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } public String add(String filename, String content) throws IOException, InterruptedException { String output = popen(filePath, listener, 0, new ArgumentListBuilder("add", filename)); if (StringUtils.isEmpty(output)) { return ""; } listener.getLogger().append(output); return output; } }