package hudson.plugins.git.util; import hudson.EnvVars; import hudson.FilePath; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Environment; import hudson.model.Hudson; import hudson.model.Node; import hudson.model.ParametersAction; import hudson.model.StreamBuildListener; import hudson.model.TaskListener; import hudson.plugins.git.Branch; import hudson.plugins.git.BranchSpec; import hudson.plugins.git.GitException; import hudson.plugins.git.GitSCM; import hudson.plugins.git.IGitAPI; import hudson.plugins.git.Revision; import hudson.slaves.NodeProperty; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.transport.RemoteConfig; public class GitUtils { public static final String DEFAULD_REPO_NAME = Constants.DEFAULT_REMOTE_NAME; IGitAPI git; TaskListener listener; public GitUtils(TaskListener listener, IGitAPI git) { this.git = git; this.listener = listener; } /** * Return a list of "Revisions" - where a revision knows about all the branch names that refer to * a SHA1. * * @return * @throws IOException * @throws GitException */ public Collection<Revision> getAllBranchRevisions() throws GitException, IOException { Map<ObjectId, Revision> revisions = new HashMap<ObjectId, Revision>(); List<Branch> branches = git.getRemoteBranches(); for (Branch b : branches) { Revision r = revisions.get(b.getSHA1()); if (r == null) { r = new Revision(b.getSHA1()); revisions.put(b.getSHA1(), r); } r.getBranches().add(b); } return revisions.values(); } /** * Return the revision containing the branch name. * * @param branchName * @return * @throws IOException * @throws GitException */ public Revision getRevisionContainingBranch(String branchName) throws GitException, IOException { for (Revision revision : getAllBranchRevisions()) { for (Branch b : revision.getBranches()) { if (b.getName().equals(branchName)) { return revision; } } } return null; } public Revision getRevisionForSHA1(ObjectId sha1) throws GitException, IOException { for (Revision revision : getAllBranchRevisions()) { if (revision.getSha1().equals(sha1)) { return revision; } } return null; } /** * Return a list of 'tip' branches (I.E. branches that aren't included entirely within another branch). * * @param git * @return */ public Collection<Revision> filterTipBranches(Collection<Revision> revisions) { // If we have 3 branches that we might want to build // ----A--.---.--- B // \-----C // we only want (B) and (C), as (A) is an ancestor (old). List<Revision> l = new ArrayList<Revision>(revisions); OUTER: for (int i = 0; i < l.size(); i++) { for (int j = i + 1; j < l.size(); j++) { Revision ri = l.get(i); Revision rj = l.get(j); ObjectId commonAncestor = git.mergeBase(ri.getSha1(), rj.getSha1()); if (commonAncestor == null) { continue; } if (commonAncestor.equals(ri.getSha1())) { LOGGER.fine("filterTipBranches: " + rj + " subsumes " + ri); l.remove(i); i--; continue OUTER; } if (commonAncestor.equals(rj.getSha1())) { LOGGER.fine("filterTipBranches: " + ri + " subsumes " + rj); l.remove(j); j--; } } } return l; } /** * An attempt to generate at least semi-useful EnvVars for polling calls, based on previous build. * Cribbed from various places. */ public static EnvVars getPollEnvironment(AbstractProject p, FilePath ws, Launcher launcher, TaskListener listener) throws IOException, InterruptedException { EnvVars env; AbstractBuild b = (AbstractBuild) p.getLastBuild(); if (b != null) { Node lastBuiltOn = b.getBuiltOn(); if (lastBuiltOn != null) { env = lastBuiltOn.toComputer().getEnvironment().overrideAll(b.getCharacteristicEnvVars()); } else { env = new EnvVars(System.getenv()); } String rootUrl = Hudson.getInstance().getRootUrl(); if (rootUrl != null) { env.put("HUDSON_URL", rootUrl); env.put("BUILD_URL", rootUrl + b.getUrl()); env.put("JOB_URL", rootUrl + p.getUrl()); } if (!env.containsKey("HUDSON_HOME")) { env.put("HUDSON_HOME", Hudson.getInstance().getRootDir().getPath()); } if (ws != null) { env.put("WORKSPACE", ws.getRemote()); } p.getScm().buildEnvVars(b, env); StreamBuildListener buildListener = new StreamBuildListener((OutputStream) listener.getLogger()); for (NodeProperty nodeProperty : Hudson.getInstance().getGlobalNodeProperties()) { Environment environment = nodeProperty.setUp(b, launcher, (BuildListener) buildListener); if (environment != null) { environment.buildEnvVars(env); } } if (lastBuiltOn != null) { for (NodeProperty nodeProperty : lastBuiltOn.getNodeProperties()) { Environment environment = nodeProperty.setUp(b, launcher, buildListener); if (environment != null) { environment.buildEnvVars(env); } } } EnvVars.resolve(env); } else { env = new EnvVars(System.getenv()); } return env; } /** * Builds branch envvars. If several branches or repos are present, env key will be generated based on pattern: * GIT_BRANCH_$repoIdx_$branchIdx. If there is only one branch and repo - {@link GitSCM#GIT_BRANCH} will be used. * * @param build {@link AbstractBuild} * @param env environment variables. * @param repositories list of repos. * @param branches list of {@link BranchSpec} */ public static void buildBranchEnvVar(AbstractBuild<?, ?> build, Map<String, String> env, List<RemoteConfig> repositories, List<BranchSpec> branches) { if (CollectionUtils.isNotEmpty(repositories) && CollectionUtils.isNotEmpty(branches)) { int branchesSize = branches.size(); int repoSize = repositories.size(); if (1 == branchesSize && 1 == repoSize) { String branch = getSingleBranch(build, repositories, branches); if (branch != null) { env.put(GitSCM.GIT_BRANCH, branch); } } else if (branchesSize > 1 || repoSize > 1) { int repoIdx = 1; int branchIdx = 1; for (RemoteConfig repo : repositories) { for (BranchSpec branchSpec : branches) { // replace repository wildcard with repository name String branchName = buildBranch(build, repo.getName(), branchSpec.getName()); if (null != branchName) { String key = GitSCM.GIT_BRANCH + "_" + repoIdx + "_" + branchIdx; env.put(key, repo.getName() + "/" + branchName); branchIdx++; } } repoIdx++; branchIdx = 1; } } } } /** * If the configuration is such that we are tracking just one branch of one repository * return that branch specifier (in the form of something like "origin/master" * <p/> * Otherwise return null. */ public static String getSingleBranch(AbstractBuild<?, ?> build, List<RemoteConfig> repositories, List<BranchSpec> branches) { // if we have multiple branches skip to advanced usecase if (branches.size() != 1 || repositories.size() != 1) { return null; } String branch = branches.get(0).getName(); String repository = repositories.get(0).getName(); return buildBranch(build, repository, branch); } /** * Builds branch based on params.Applied build params if any. Returns null if branch contains *, * * @param build {@link AbstractBuild} * @param repository repo name * @param branchName branch name * @return branch name */ public static String buildBranch(AbstractBuild<?, ?> build, String repository, String branchName) { // replace repository wildcard with repository name if (branchName.startsWith("*/")) { branchName = repository + branchName.substring(1); } // if the branchName name contains more wildcards then the simple usecase // does not apply and we need to skip to the advanced usecase if (branchName.contains("*")) { return null; } // substitute build parameters if available ParametersAction parameters = build.getAction(ParametersAction.class); if (parameters != null) { branchName = parameters.substitute(build, branchName); } return branchName; } public static String[] fixupNames(String[] names, String[] urls) { String[] returnNames = new String[urls.length]; Set<String> usedNames = new HashSet<String>(); for (int i = 0; i < urls.length; i++) { String name = names[i]; if (name == null || name.trim().length() == 0) { name = DEFAULD_REPO_NAME; } String baseName = name; int j = 1; while (usedNames.contains(name)) { name = baseName + (j++); } usedNames.add(name); returnNames[i] = name; } return returnNames; } /** * Verifies whether array of the repository Urls is empty. * * @param urls repository Urls. * @return true if array is empty. */ public static boolean isEmpty(String[] urls) { if (ArrayUtils.isEmpty(urls)) { return true; } for (String url : urls) { if (StringUtils.isNotEmpty(url)) { return false; } } return true; } /** * Verifies if first collection equals second without ordering. * @param c1 collection * @param c2 collection * @return true if collections equal. */ public static boolean isEqualCollection(Collection c1, Collection c2) { if (c1 == c2) { return true; } if (c1 == null && c2 == null) { return true; } if (c1 == null || c2 == null) { return false; } return CollectionUtils.isEqualCollection(c1, c2); } /** * Verifies if first array equals second without ordering. * @param o1 array * @param o2 array * @return true if arrays equal. */ public static boolean isEqualArray(Object[] o1, Object[] o2) { if (o1 == o2) { return true; } if (o1 == null && o2 == null) { return true; } if (o1 == null || o2 == null) { return false; } return CollectionUtils.isEqualCollection(Arrays.asList(o1), Arrays.asList(o2)); } private static final Logger LOGGER = Logger.getLogger(GitUtils.class.getName()); }