package hudson.plugins.svnmerge; import hudson.BulkChange; import hudson.Util; import hudson.model.AbstractModelObject; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.Hudson; import hudson.scm.SCM; import hudson.scm.SubversionSCM; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.SVNClientManager; import org.tmatesoft.svn.core.wc.SVNInfo; import org.tmatesoft.svn.core.wc.SVNRevision; import javax.servlet.ServletException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Project-level {@link Action} that shows the feature branches. * * <p> * This is attached to the upstream job. * * @author Kohsuke Kawaguchi */ public class IntegratableProjectAction extends AbstractModelObject implements Action { public final AbstractProject<?,?> project; private final IntegratableProject ip; /*package*/ IntegratableProjectAction(IntegratableProject ip) { this.ip = ip; this.project = ip.getOwner(); } public String getIconFileName() { return "/plugin/svnmerge/24x24/sync.gif"; } public String getDisplayName() { return "Feature Branches"; } public String getSearchUrl() { return getDisplayName(); } public String getUrlName() { return "featureBranches"; } /** * Gets feature branches for this project. */ public List<AbstractProject<?,?>> getBranches() { String n = project.getName(); List<AbstractProject<?,?>> r = new ArrayList<AbstractProject<?,?>>(); for (AbstractProject<?,?> p : Hudson.getInstance().getItems(AbstractProject.class)) { FeatureBranchProperty fbp = p.getProperty(FeatureBranchProperty.class); if(fbp!=null && fbp.getUpstream().equals(n)) r.add(p); } return r; } public void doNewBranch(StaplerRequest req, StaplerResponse rsp, @QueryParameter String name, @QueryParameter boolean attach) throws ServletException, IOException { requirePOST(); name = Util.fixEmptyAndTrim(name); if (name==null) { sendError("Name is required"); return; } SCM scm = project.getScm(); if (!(scm instanceof SubversionSCM)) { sendError("This project doesn't use Subversion as SCM"); return; } // TODO: check for multiple locations SubversionSCM svn = (SubversionSCM) scm; String url = svn.getLocations()[0].getURL(); Matcher m = KEYWORD.matcher(url); if(!m.find()) { sendError("Unable to infer the new branch name from "+url); return; } url = url.substring(0,m.start())+"/branches/"+name; if(!attach) { SVNClientManager svnm = SubversionSCM.createSvnClientManager(); try { SVNURL dst = SVNURL.parseURIEncoded(url); // check if the branch already exists try { SVNInfo info = svnm.getWCClient().doInfo(dst, null, null); if(info.getKind()== SVNNodeKind.DIR) { // ask the user if we should attach req.getView(this,"_attach.jelly").forward(req,rsp); return; } else { sendError(info.getURL()+" already exists."); return; } } catch (SVNException e) { // path doesn't exist, which is good } // create a branch svnm.getCopyClient().doCopy( svn.getLocations()[0].getSVNURL(), SVNRevision.HEAD, dst, false, true, "Created a feature branch from Hudson"); } catch (SVNException e) { sendError(e); return; } } // copy a job, and adjust its properties for integration AbstractProject<?,?> copy = Hudson.getInstance().copy(project, project.getName() + "-" + name); BulkChange bc = new BulkChange(copy); try { copy.removeProperty(IntegratableProject.class); ((AbstractProject)copy).addProperty(new FeatureBranchProperty(project.getName())); // pointless cast for working around javac bug as of JDK1.6.0_02 // update the SCM config to point to the branch SubversionSCM svnScm = (SubversionSCM)copy.getScm(); copy.setScm(new SubversionSCM(new String[]{url},new String[]{null},svnScm.isUseUpdate(),svnScm.getBrowser())); } finally { bc.commit(); } rsp.sendRedirect2(req.getContextPath()+"/"+copy.getUrl()); } private static final Pattern KEYWORD = Pattern.compile("/(trunk(/|$)|branches/)"); }