Mercurial > hg > mercurial
annotate mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java @ 19:40b2cf04cd4b
some comments added
author | Pavel.Sher |
---|---|
date | Wed, 16 Jul 2008 17:37:41 +0400 |
parents | d787c696225c |
children | 90f5e574fb73 |
rev | line source |
---|---|
0 | 1 package jetbrains.buildServer.buildTriggers.vcs.mercurial; |
2 | |
3 import jetbrains.buildServer.CollectChangesByIncludeRule; | |
4 import jetbrains.buildServer.Used; | |
5 import jetbrains.buildServer.buildTriggers.vcs.AbstractVcsPropertiesProcessor; | |
6 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; | |
7 import jetbrains.buildServer.log.Loggers; | |
8 import jetbrains.buildServer.serverSide.InvalidProperty; | |
9 import jetbrains.buildServer.serverSide.PropertiesProcessor; | |
10 import jetbrains.buildServer.serverSide.ServerPaths; | |
11 import jetbrains.buildServer.util.FileUtil; | |
12 import jetbrains.buildServer.vcs.*; | |
13 import jetbrains.buildServer.vcs.patches.PatchBuilder; | |
14 import org.jetbrains.annotations.NotNull; | |
15 import org.jetbrains.annotations.Nullable; | |
16 | |
1 | 17 import java.io.File; |
18 import java.io.FileFilter; | |
19 import java.io.FileInputStream; | |
20 import java.io.IOException; | |
21 import java.util.*; | |
22 | |
19 | 23 /** |
24 * Mercurial VCS plugin for TeamCity works as follows: | |
25 * <ul> | |
26 * <li>clones repository to internal storage | |
27 * <li>before any operation with working copy of repository pulls changes from the original repository | |
28 * <li>executes corresponding hg command | |
29 * </ul> | |
30 * | |
31 * <p>Working copy of repository is created in the $TEAMCITY_DATA_PATH/system/caches/mercurial folder. | |
32 * <p>Personal builds (remote runs) are not yet supported, they require corresponding functionality from the IDE. | |
33 * <p>Checkout on agent mode is not yet supported too. | |
34 */ | |
0 | 35 public class MercurialVcsSupport extends VcsSupport implements CollectChangesByIncludeRule { |
36 private ServerPaths myServerPaths; | |
37 | |
38 public MercurialVcsSupport(@NotNull VcsManager vcsManager, @NotNull ServerPaths paths) { | |
39 vcsManager.registerVcsSupport(this); | |
40 myServerPaths = paths; | |
41 } | |
42 | |
43 public List<ModificationData> collectBuildChanges(final VcsRoot root, | |
44 @NotNull final String fromVersion, | |
45 @NotNull final String currentVersion, | |
46 final CheckoutRules checkoutRules) throws VcsException { | |
47 updateWorkingDirectory(root); | |
48 return VcsSupportUtil.collectBuildChanges(root, fromVersion, currentVersion, checkoutRules, this); | |
49 } | |
50 | |
51 public List<ModificationData> collectBuildChanges(final VcsRoot root, | |
52 final String fromVersion, | |
53 final String currentVersion, | |
54 final IncludeRule includeRule) throws VcsException { | |
19 | 55 // first obtain changes between specified versions |
0 | 56 List<ModificationData> result = new ArrayList<ModificationData>(); |
57 Settings settings = new Settings(myServerPaths, root); | |
58 LogCommand lc = new LogCommand(settings); | |
59 lc.setFromRevId(new ChangeSet(fromVersion).getId()); | |
60 lc.setToRevId(new ChangeSet(currentVersion).getId()); | |
61 List<ChangeSet> changeSets = lc.execute(); | |
62 if (changeSets.isEmpty()) { | |
63 return result; | |
64 } | |
65 | |
19 | 66 // invoke status command for each changeset and determine what files were modified in these changesets |
0 | 67 Iterator<ChangeSet> it = changeSets.iterator(); |
68 ChangeSet prev = it.next(); // skip first changeset (cause it was already reported) | |
69 StatusCommand st = new StatusCommand(settings); | |
70 while (it.hasNext()) { | |
71 ChangeSet cur = it.next(); | |
72 st.setFromRevId(prev.getId()); | |
73 st.setToRevId(cur.getId()); | |
74 List<ModifiedFile> modifiedFiles = st.execute(); | |
19 | 75 // changeset full version will be set into VcsChange structure and |
76 // stored in database (note than getContent method will be invoked with this version) | |
0 | 77 List<VcsChange> files = toVcsChanges(modifiedFiles, prev.getFullVersion(), cur.getFullVersion(), includeRule); |
78 if (files.isEmpty()) continue; | |
79 ModificationData md = new ModificationData(cur.getTimestamp(), files, cur.getSummary(), cur.getUser(), root, cur.getFullVersion(), cur.getFullVersion()); | |
80 result.add(md); | |
81 prev = cur; | |
82 } | |
83 | |
84 return result; | |
85 } | |
86 | |
87 private List<VcsChange> toVcsChanges(final List<ModifiedFile> modifiedFiles, String prevVer, String curVer, final IncludeRule includeRule) { | |
88 List<VcsChange> files = new ArrayList<VcsChange>(); | |
89 for (ModifiedFile mf: modifiedFiles) { | |
12 | 90 String normalizedPath = normalizePath(mf.getPath()); |
91 if (!normalizedPath.startsWith(includeRule.getFrom())) continue; // skip files which do not match include rule | |
0 | 92 |
93 VcsChangeInfo.Type changeType = getChangeType(mf.getStatus()); | |
94 if (changeType == null) { | |
95 Loggers.VCS.warn("Unable to convert status: " + mf.getStatus() + " to VCS change type"); | |
96 changeType = VcsChangeInfo.Type.NOT_CHANGED; | |
97 } | |
12 | 98 files.add(new VcsChange(changeType, mf.getStatus().getName(), normalizedPath, normalizedPath, prevVer, curVer)); |
0 | 99 } |
100 return files; | |
101 } | |
102 | |
103 private @NotNull String normalizePath(@NotNull String repPath) { | |
104 return repPath.replace('\\', '/'); | |
105 } | |
106 | |
107 private VcsChangeInfo.Type getChangeType(final ModifiedFile.Status status) { | |
108 switch (status) { | |
109 case ADDED:return VcsChangeInfo.Type.ADDED; | |
110 case MODIFIED:return VcsChangeInfo.Type.CHANGED; | |
111 case REMOVED:return VcsChangeInfo.Type.REMOVED; | |
112 } | |
113 return null; | |
114 } | |
115 | |
116 @NotNull | |
117 public byte[] getContent(final VcsModification vcsModification, | |
118 final VcsChangeInfo change, | |
119 final VcsChangeInfo.ContentType contentType, | |
120 final VcsRoot vcsRoot) throws VcsException { | |
12 | 121 updateWorkingDirectory(vcsRoot); |
122 String version = contentType == VcsChangeInfo.ContentType.AFTER_CHANGE ? change.getAfterChangeRevisionNumber() : change.getBeforeChangeRevisionNumber(); | |
123 return getContent(change.getRelativeFileName(), vcsRoot, version); | |
0 | 124 } |
125 | |
126 @NotNull | |
12 | 127 public byte[] getContent(final String filePath, final VcsRoot vcsRoot, final String version) throws VcsException { |
128 updateWorkingDirectory(vcsRoot); | |
129 Settings settings = new Settings(myServerPaths, vcsRoot); | |
130 CatCommand cc = new CatCommand(settings); | |
131 ChangeSet cs = new ChangeSet(version); | |
132 cc.setRevId(cs.getId()); | |
133 File parentDir = cc.execute(Collections.singletonList(filePath)); | |
134 File file = new File(parentDir, filePath); | |
135 if (file.isFile()) { | |
136 try { | |
137 return FileUtil.loadFileBytes(file); | |
138 } catch (IOException e) { | |
139 throw new VcsException("Failed to load content of file: " + file.getAbsolutePath(), e); | |
140 } | |
141 } else { | |
142 Loggers.VCS.warn("Unable to obtain content of the file: " + filePath); | |
143 } | |
0 | 144 return new byte[0]; |
145 } | |
146 | |
147 public String getName() { | |
148 return "mercurial"; | |
149 } | |
150 | |
151 @Used("jsp") | |
152 public String getDisplayName() { | |
153 return "Mercurial"; | |
154 } | |
155 | |
156 @Nullable | |
157 public PropertiesProcessor getVcsPropertiesProcessor() { | |
158 return new AbstractVcsPropertiesProcessor() { | |
159 public Collection<InvalidProperty> process(final Map<String, String> properties) { | |
160 List<InvalidProperty> result = new ArrayList<InvalidProperty>(); | |
161 if (isEmpty(properties.get(Constants.HG_COMMAND_PATH_PROP))) { | |
162 result.add(new InvalidProperty(Constants.HG_COMMAND_PATH_PROP, "Path to 'hg' command must be specified")); | |
163 } | |
164 if (isEmpty(properties.get(Constants.REPOSITORY_PROP))) { | |
165 result.add(new InvalidProperty(Constants.REPOSITORY_PROP, "Repository must be specified")); | |
166 } | |
167 return result; | |
168 } | |
169 }; | |
170 } | |
171 | |
172 public String getVcsSettingsJspFilePath() { | |
173 return "mercurialSettings.jsp"; | |
174 } | |
175 | |
176 @NotNull | |
177 public String getCurrentVersion(final VcsRoot root) throws VcsException { | |
19 | 178 // we will return full version of the most recent change as current version |
0 | 179 updateWorkingDirectory(root); |
180 Settings settings = new Settings(myServerPaths, root); | |
9
7dadebd03515
use tip command instead of log in getCurrentVersion method
Pavel.Sher
parents:
8
diff
changeset
|
181 TipCommand lc = new TipCommand(settings); |
7dadebd03515
use tip command instead of log in getCurrentVersion method
Pavel.Sher
parents:
8
diff
changeset
|
182 ChangeSet changeSet = lc.execute(); |
0 | 183 return changeSet.getFullVersion(); |
184 } | |
185 | |
186 public String describeVcsRoot(final VcsRoot vcsRoot) { | |
187 return "mercurial: " + vcsRoot.getProperty(Constants.REPOSITORY_PROP); | |
188 } | |
189 | |
190 public boolean isTestConnectionSupported() { | |
16
7aa397165fa0
identify command added, test connection now uses identify command
Pavel.Sher
parents:
13
diff
changeset
|
191 return true; |
0 | 192 } |
193 | |
194 @Nullable | |
195 public String testConnection(final VcsRoot vcsRoot) throws VcsException { | |
16
7aa397165fa0
identify command added, test connection now uses identify command
Pavel.Sher
parents:
13
diff
changeset
|
196 Settings settings = new Settings(myServerPaths, vcsRoot); |
7aa397165fa0
identify command added, test connection now uses identify command
Pavel.Sher
parents:
13
diff
changeset
|
197 IdentifyCommand id = new IdentifyCommand(settings); |
7aa397165fa0
identify command added, test connection now uses identify command
Pavel.Sher
parents:
13
diff
changeset
|
198 StringBuilder res = new StringBuilder(); |
17 | 199 res.append(quoteIfNeeded(settings.getHgCommandPath())); |
200 res.append(" identify "); | |
201 res.append(quoteIfNeeded(settings.getRepository())); | |
16
7aa397165fa0
identify command added, test connection now uses identify command
Pavel.Sher
parents:
13
diff
changeset
|
202 res.append('\n').append(id.execute()); |
7aa397165fa0
identify command added, test connection now uses identify command
Pavel.Sher
parents:
13
diff
changeset
|
203 return res.toString(); |
0 | 204 } |
205 | |
17 | 206 private String quoteIfNeeded(@NotNull String str) { |
207 if (str.indexOf(' ') != -1) { | |
208 return "\"" + str + "\""; | |
209 } | |
210 | |
211 return str; | |
212 } | |
213 | |
0 | 214 @Nullable |
215 public Map<String, String> getDefaultVcsProperties() { | |
19 | 216 return Collections.singletonMap(Constants.HG_COMMAND_PATH_PROP, "hg"); |
0 | 217 } |
218 | |
219 public String getVersionDisplayName(final String version, final VcsRoot root) throws VcsException { | |
220 return version; | |
221 } | |
222 | |
223 @NotNull | |
224 public Comparator<String> getVersionComparator() { | |
19 | 225 // comparator is called when TeamCity needs to sort modifications in the order of their appearance, |
226 // currently we sort changes by revision number, not sure however that this is a good idea, | |
227 // probably it would be better to sort them by timestamp (and to add timestamp into the version). | |
0 | 228 return new Comparator<String>() { |
229 public int compare(final String o1, final String o2) { | |
230 try { | |
231 return new ChangeSet(o1).getRevNumber() - new ChangeSet(o2).getRevNumber(); | |
232 } catch (Exception e) { | |
233 return 1; | |
234 } | |
235 } | |
236 }; | |
237 } | |
238 | |
239 public void buildPatch(final VcsRoot root, | |
240 @Nullable final String fromVersion, | |
241 @NotNull final String toVersion, | |
242 final PatchBuilder builder, | |
243 final CheckoutRules checkoutRules) throws IOException, VcsException { | |
244 updateWorkingDirectory(root); | |
245 Settings settings = new Settings(myServerPaths, root); | |
246 if (fromVersion == null) { | |
247 buildFullPatch(settings, new ChangeSet(toVersion), builder); | |
248 } else { | |
249 buildIncrementalPatch(settings, new ChangeSet(fromVersion), new ChangeSet(toVersion), builder); | |
250 } | |
251 } | |
252 | |
19 | 253 // builds patch from version to version |
0 | 254 private void buildIncrementalPatch(final Settings settings, @NotNull final ChangeSet fromVer, @NotNull final ChangeSet toVer, final PatchBuilder builder) |
255 throws VcsException, IOException { | |
256 StatusCommand st = new StatusCommand(settings); | |
257 st.setFromRevId(fromVer.getId()); | |
258 st.setToRevId(toVer.getId()); | |
259 List<ModifiedFile> modifiedFiles = st.execute(); | |
260 List<String> notDeletedFiles = new ArrayList<String>(); | |
261 for (ModifiedFile f: modifiedFiles) { | |
262 if (f.getStatus() != ModifiedFile.Status.REMOVED) { | |
263 notDeletedFiles.add(f.getPath()); | |
264 } | |
265 } | |
266 | |
267 CatCommand cc = new CatCommand(settings); | |
268 cc.setRevId(toVer.getId()); | |
269 File parentDir = cc.execute(notDeletedFiles); | |
270 | |
271 try { | |
272 for (ModifiedFile f: modifiedFiles) { | |
273 final File virtualFile = new File(f.getPath()); | |
274 if (f.getStatus() == ModifiedFile.Status.REMOVED) { | |
275 builder.deleteFile(virtualFile, true); | |
276 } else { | |
277 File realFile = new File(parentDir, f.getPath()); | |
278 FileInputStream is = new FileInputStream(realFile); | |
279 try { | |
11 | 280 builder.changeOrCreateBinaryFile(virtualFile, null, is, realFile.length()); |
0 | 281 } finally { |
282 is.close(); | |
283 } | |
284 } | |
285 } | |
286 } finally { | |
287 FileUtil.delete(parentDir); | |
288 } | |
289 } | |
290 | |
19 | 291 // builds patch by exporting files using specified version |
0 | 292 private void buildFullPatch(final Settings settings, @NotNull final ChangeSet toVer, final PatchBuilder builder) |
293 throws IOException, VcsException { | |
294 CloneCommand cl = new CloneCommand(settings); | |
295 cl.setToId(toVer.getId()); | |
296 File tempDir = FileUtil.createTempDirectory("mercurial", toVer.getId()); | |
297 try { | |
298 final File repRoot = new File(tempDir, "rep"); | |
299 cl.setDestDir(repRoot.getAbsolutePath()); | |
300 cl.execute(); | |
301 buildPatchFromDirectory(builder, repRoot, new FileFilter() { | |
302 public boolean accept(final File file) { | |
303 return !(file.isDirectory() && ".hg".equals(file.getName())); | |
304 } | |
305 }); | |
306 } finally { | |
307 FileUtil.delete(tempDir); | |
308 } | |
309 } | |
310 | |
311 private void buildPatchFromDirectory(final PatchBuilder builder, final File repRoot, final FileFilter filter) throws IOException { | |
312 buildPatchFromDirectory(repRoot, builder, repRoot, filter); | |
313 } | |
314 | |
315 private void buildPatchFromDirectory(File curDir, final PatchBuilder builder, final File repRoot, final FileFilter filter) throws IOException { | |
316 File[] files = curDir.listFiles(filter); | |
317 if (files != null) { | |
318 for (File realFile: files) { | |
319 String relPath = realFile.getAbsolutePath().substring(repRoot.getAbsolutePath().length()); | |
320 final File virtualFile = new File(relPath); | |
321 if (realFile.isDirectory()) { | |
322 builder.createDirectory(virtualFile); | |
323 buildPatchFromDirectory(realFile, builder, repRoot, filter); | |
324 } else { | |
325 final FileInputStream is = new FileInputStream(realFile); | |
326 try { | |
327 builder.createBinaryFile(virtualFile, null, is, realFile.length()); | |
328 } finally { | |
329 is.close(); | |
330 } | |
331 } | |
332 } | |
333 } | |
334 } | |
335 | |
19 | 336 // updates current working copy of repository by pulling changes from the repository specified in VCS root |
0 | 337 private void updateWorkingDirectory(final VcsRoot root) throws VcsException { |
338 Settings settings = new Settings(myServerPaths, root); | |
339 String workDir = settings.getWorkingDir(); | |
340 synchronized (root) { | |
341 if (hasRepositoryCopy(new File(workDir))) { | |
342 // update | |
8 | 343 PullCommand pull = new PullCommand(settings); |
344 pull.execute(); | |
0 | 345 } else { |
346 // clone | |
347 CloneCommand cl = new CloneCommand(settings); | |
348 cl.setDestDir(workDir); | |
18 | 349 cl.setUpdateWorkingDir(false); |
0 | 350 cl.execute(); |
351 } | |
352 } | |
353 } | |
354 | |
355 private boolean hasRepositoryCopy(final File workDir) { | |
19 | 356 // need better way to check that repository copy is ok |
0 | 357 return workDir.isDirectory() && new File(workDir, ".hg").isDirectory(); |
358 } | |
359 | |
360 } |