/
IntegratableProjectAction.java
273 lines (239 loc) · 9.72 KB
/
IntegratableProjectAction.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
package jenkins.plugins.svnmerge;
import hudson.BulkChange;
import hudson.Util;
import hudson.model.Action;
import hudson.model.AbstractModelObject;
import hudson.model.AbstractProject;
import hudson.scm.SvnClientManager;
import hudson.scm.SCM;
import hudson.scm.SubversionSCM;
import hudson.scm.SubversionSCM.ModuleLocation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.wc.SVNInfo;
import org.tmatesoft.svn.core.wc.SVNRevision;
/**
* 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 : Jenkins.getInstance().getItems(AbstractProject.class)) {
FeatureBranchProperty fbp = p.getProperty(FeatureBranchProperty.class);
if(fbp!=null && fbp.getUpstream().equals(n))
r.add(p);
}
return r;
}
/**
*
* @return
*/
public RepositoryLayoutInfo getRepositoryLayout() {
SCM scm = project.getScm();
if (!(scm instanceof SubversionSCM)) {
return null;
}
//TODO: check for multiple locations ?
SubversionSCM svn = (SubversionSCM) scm;
ModuleLocation firstLocation = svn.getLocations()[0];
// expand system and node environment variables as well as the project parameters
firstLocation = Utility.getExpandedLocation(firstLocation, project);
return getRepositoryLayout(firstLocation);
}
private RepositoryLayoutInfo getRepositoryLayout(ModuleLocation location) {
return new RepositoryLayoutInfo(location.getURL());
}
@RequirePOST
public void doNewBranch(StaplerRequest req, StaplerResponse rsp,
@QueryParameter String name,
@QueryParameter boolean attach,
@QueryParameter String commitMessage,
@QueryParameter String branchLocation,
@QueryParameter boolean createTag,
@QueryParameter String tagLocation) throws ServletException, IOException {
name = Util.fixEmptyAndTrim(name);
if (name==null) {
sendError("Name is required");
return;
}
commitMessage = Util.fixEmptyAndTrim(commitMessage);
if (commitMessage==null) {
//the commit message isn't used when attaching to an existing location
commitMessage = "Created a feature branch from Jenkins with name: "+name;
}
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;
ModuleLocation firstLocation = svn.getLocations()[0];
// expand system and node environment variables as well as the project parameters
firstLocation = Utility.getExpandedLocation(firstLocation, project);
RepositoryLayoutInfo layoutInfo = getRepositoryLayout(firstLocation);
branchLocation = Util.fixEmptyAndTrim(branchLocation);
tagLocation = Util.fixEmptyAndTrim(tagLocation);
if (layoutInfo.getLayout() == RepositoryLayoutEnum.CUSTOM) {
/*
* in case of custom layout the user must provide the full new branch url
* (and optionally the full new tag url)
*/
if (StringUtils.isEmpty(branchLocation)) {
sendError("Branch Location is required for custom repository layout");
}
if (!attach && createTag && StringUtils.isEmpty(tagLocation)) {
sendError("Tag Location is required for custom repository layout");
}
}
String branchUrl;
if (StringUtils.isNotEmpty(branchLocation)) {
//using override value
branchUrl = branchLocation;
} else {
//using default value
branchUrl = layoutInfo.getDefaultNewBranchUrl().replace("<new_branch_name>", name);
}
if (!attach) {
SvnClientManager svnMgr = SubversionSCM.createClientManager(
svn.createAuthenticationProvider(project, firstLocation));
List<String> urlsToCopyTo = new ArrayList<String>();
SVNURL svnUrl = null;
try {
svnUrl = SVNURL.parseURIEncoded(branchUrl);
SVNInfo info = svnMgr.getWCClient().doInfo(svnUrl, SVNRevision.HEAD, SVNRevision.HEAD);
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, the new branch can be created
}
urlsToCopyTo.add(branchUrl);
String tagUrl = null;
if (createTag) {
//can be true only when not attaching
if (StringUtils.isNotEmpty(tagLocation)) {
//using override value
tagUrl = tagLocation;
} else {
//using default value
tagUrl = layoutInfo.getDefaultNewDevTagUrl().replace("<new_branch_name>", name);
}
try {
svnUrl = SVNURL.parseURIEncoded(tagUrl);
SVNInfo info = svnMgr.getWCClient().doInfo(svnUrl, SVNRevision.HEAD, SVNRevision.HEAD);
sendError(info.getURL()+" already exists.");
return;
} catch (SVNException e) {
// path doesn't exist, the new tag can be created
}
urlsToCopyTo.add(tagUrl);
}
if (!createSVNCopy(svnMgr, firstLocation, urlsToCopyTo, commitMessage, req, rsp)) {
return;
}
}
// if the request wasn't forwarded
// copy a job, and adjust its properties for integration
AbstractProject<?,?> copy = Jenkins.getInstance().copy(project, project.getName() + "-" + name.replaceAll("/", "-"));
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(
Arrays.asList(firstLocation.withRemote(branchUrl)),
svnScm.getWorkspaceUpdater(),
svnScm.getBrowser(),
svnScm.getExcludedRegions(),
svnScm.getExcludedUsers(),
svnScm.getExcludedRevprop(),
svnScm.getExcludedCommitMessages(),
svnScm.getIncludedRegions(),
svnScm.isIgnoreDirPropChanges(),
svnScm.isFilterChangelog(),
svnScm.getAdditionalCredentials()
));
} finally {
bc.commit();
}
rsp.sendRedirect2(req.getContextPath()+"/"+copy.getUrl());
}
/**
* Utility method for SVN copies creation.
* First checks if all the given urls already exist; if any exist, creates a copy for each of them.
* @param scm the project scm
* @param urlsToCopyTo a list of urls to copy to (i.e. where the copies'll be created).
* @param commitMessage the commit message to use
* @param req the original StaplerRequest
* @param rsp the original StaplerResponse
* @throws ServletException
* @throws IOException
*/
private boolean createSVNCopy(SvnClientManager svnMgr, ModuleLocation originalLocation, List<String> urlsToCopyTo,
String commitMessage, StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException {
try {
for (String urlToCopyTo : urlsToCopyTo) {
SVNURL dst = SVNURL.parseURIEncoded(urlToCopyTo);
svnMgr.getCopyClient().doCopy(originalLocation.getSVNURL(),
SVNRevision.HEAD,
dst,
false,
true,
commitMessage
);
}
return true;
} catch (SVNException e) {
sendError(e);
return false;
}
}
}