Mercurial > hg > mercurial
annotate mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java @ 121:e8125034f6a9
fix cleanup
author | Pavel.Sher |
---|---|
date | Fri, 24 Sep 2010 17:37:27 +0400 |
parents | 8587a9c22d55 |
children | 62fe3e69cee6 43dd4142b0f5 |
rev | line source |
---|---|
25 | 1 /* |
95 | 2 * Copyright 2000-2010 JetBrains s.r.o. |
25 | 3 * |
4 * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 * you may not use this file except in compliance with the License. | |
6 * You may obtain a copy of the License at | |
7 * | |
8 * http://www.apache.org/licenses/LICENSE-2.0 | |
9 * | |
10 * Unless required by applicable law or agreed to in writing, software | |
11 * distributed under the License is distributed on an "AS IS" BASIS, | |
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 * See the License for the specific language governing permissions and | |
14 * limitations under the License. | |
15 */ | |
0 | 16 package jetbrains.buildServer.buildTriggers.vcs.mercurial; |
17 | |
103 | 18 import jetbrains.buildServer.BuildAgent; |
0 | 19 import jetbrains.buildServer.Used; |
20 import jetbrains.buildServer.buildTriggers.vcs.AbstractVcsPropertiesProcessor; | |
21 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; | |
22 import jetbrains.buildServer.log.Loggers; | |
103 | 23 import jetbrains.buildServer.serverSide.*; |
24 import jetbrains.buildServer.util.EventDispatcher; | |
0 | 25 import jetbrains.buildServer.util.FileUtil; |
62
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
26 import jetbrains.buildServer.util.StringUtil; |
103 | 27 import jetbrains.buildServer.util.filters.Filter; |
28 import jetbrains.buildServer.util.filters.FilterUtil; | |
0 | 29 import jetbrains.buildServer.vcs.*; |
30 import jetbrains.buildServer.vcs.patches.PatchBuilder; | |
31 import org.jetbrains.annotations.NotNull; | |
32 import org.jetbrains.annotations.Nullable; | |
33 | |
1 | 34 import java.io.File; |
35 import java.io.FileFilter; | |
36 import java.io.FileInputStream; | |
37 import java.io.IOException; | |
38 import java.util.*; | |
21 | 39 import java.util.concurrent.ConcurrentHashMap; |
40 import java.util.concurrent.ConcurrentMap; | |
41 import java.util.concurrent.locks.Lock; | |
42 import java.util.concurrent.locks.ReentrantLock; | |
1 | 43 |
19 | 44 /** |
45 * Mercurial VCS plugin for TeamCity works as follows: | |
46 * <ul> | |
47 * <li>clones repository to internal storage | |
48 * <li>before any operation with working copy of repository pulls changes from the original repository | |
49 * <li>executes corresponding hg command | |
50 * </ul> | |
51 * | |
57 | 52 * <p>Working copy of repository is created in the $TEAMCITY_DATA_PATH/system/caches/hg_<hash code> folder. |
19 | 53 * <p>Personal builds (remote runs) are not yet supported, they require corresponding functionality from the IDE. |
54 */ | |
106 | 55 public class MercurialVcsSupport extends ServerVcsSupport implements LabelingSupport, VcsFileContentProvider { |
21 | 56 private ConcurrentMap<String, Lock> myWorkDirLocks= new ConcurrentHashMap<String, Lock>(); |
57 private VcsManager myVcsManager; | |
27
7944e8985ebd
prepare modules structure for agent side checkout
Pavel.Sher
parents:
25
diff
changeset
|
58 private File myDefaultWorkFolderParent; |
0 | 59 |
21 | 60 public MercurialVcsSupport(@NotNull final VcsManager vcsManager, |
61 @NotNull ServerPaths paths, | |
103 | 62 @NotNull final SBuildServer server, |
63 @NotNull EventDispatcher<BuildServerListener> dispatcher) { | |
21 | 64 myVcsManager = vcsManager; |
62
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
65 myDefaultWorkFolderParent = new File(paths.getCachesDir(), "mercurial"); |
103 | 66 dispatcher.addListener(new BuildServerAdapter() { |
67 @Override | |
121 | 68 public void cleanupFinished() { |
69 super.cleanupFinished(); | |
70 server.getExecutor().submit(new Runnable() { | |
71 public void run() { | |
72 removeOldWorkFolders(); | |
73 } | |
74 }); | |
75 } | |
76 | |
77 @Override | |
103 | 78 public void sourcesVersionReleased(@NotNull final BuildAgent agent) { |
79 super.sourcesVersionReleased(agent); | |
80 server.getExecutor().submit(new Runnable() { | |
81 public void run() { | |
82 Set<File> clonedRepos = getAllClonedRepos(); | |
83 if (clonedRepos == null) return; | |
84 for (File f: clonedRepos) { | |
85 lockWorkDir(f); | |
86 try { | |
87 FileUtil.delete(f); | |
88 } finally { | |
89 unlockWorkDir(f); | |
90 } | |
91 } | |
92 } | |
93 }); | |
94 } | |
95 }); | |
0 | 96 } |
97 | |
90 | 98 private Collection<ModifiedFile> computeModifiedFilesForMergeCommit(final Settings settings, final ChangeSet cur) throws VcsException { |
99 if (!cur.containsFiles()) return Collections.emptyList(); | |
100 | |
101 ChangedFilesCommand cfc = new ChangedFilesCommand(settings); | |
102 cfc.setRevId(cur.getId()); | |
103 return cfc.execute(); | |
104 } | |
105 | |
0 | 106 private List<VcsChange> toVcsChanges(final List<ModifiedFile> modifiedFiles, String prevVer, String curVer, final IncludeRule includeRule) { |
107 List<VcsChange> files = new ArrayList<VcsChange>(); | |
108 for (ModifiedFile mf: modifiedFiles) { | |
21 | 109 String normalizedPath = PathUtil.normalizeSeparator(mf.getPath()); |
12 | 110 if (!normalizedPath.startsWith(includeRule.getFrom())) continue; // skip files which do not match include rule |
106 | 111 String relPath = StringUtil.removeLeadingSlash(normalizedPath.substring(includeRule.getFrom().length())); |
0 | 112 |
113 VcsChangeInfo.Type changeType = getChangeType(mf.getStatus()); | |
114 if (changeType == null) { | |
115 Loggers.VCS.warn("Unable to convert status: " + mf.getStatus() + " to VCS change type"); | |
116 changeType = VcsChangeInfo.Type.NOT_CHANGED; | |
117 } | |
106 | 118 files.add(new VcsChange(changeType, mf.getStatus().getName(), normalizedPath, relPath, prevVer, curVer)); |
0 | 119 } |
120 return files; | |
121 } | |
122 | |
123 private VcsChangeInfo.Type getChangeType(final ModifiedFile.Status status) { | |
124 switch (status) { | |
125 case ADDED:return VcsChangeInfo.Type.ADDED; | |
126 case MODIFIED:return VcsChangeInfo.Type.CHANGED; | |
127 case REMOVED:return VcsChangeInfo.Type.REMOVED; | |
128 } | |
129 return null; | |
130 } | |
131 | |
132 @NotNull | |
86 | 133 public byte[] getContent(@NotNull final VcsModification vcsModification, |
134 @NotNull final VcsChangeInfo change, | |
135 @NotNull final VcsChangeInfo.ContentType contentType, | |
136 @NotNull final VcsRoot vcsRoot) throws VcsException { | |
44 | 137 syncClonedRepository(vcsRoot); |
12 | 138 String version = contentType == VcsChangeInfo.ContentType.AFTER_CHANGE ? change.getAfterChangeRevisionNumber() : change.getBeforeChangeRevisionNumber(); |
139 return getContent(change.getRelativeFileName(), vcsRoot, version); | |
0 | 140 } |
141 | |
142 @NotNull | |
86 | 143 public byte[] getContent(@NotNull final String filePath, @NotNull final VcsRoot vcsRoot, @NotNull final String version) throws VcsException { |
44 | 144 syncClonedRepository(vcsRoot); |
145 Settings settings = createSettings(vcsRoot); | |
12 | 146 CatCommand cc = new CatCommand(settings); |
59
30cc10fe9479
accept version in two formats: with rev number and without
Pavel.Sher
parents:
58
diff
changeset
|
147 cc.setRevId(new ChangeSet(version).getId()); |
12 | 148 File parentDir = cc.execute(Collections.singletonList(filePath)); |
30 | 149 try { |
150 File file = new File(parentDir, filePath); | |
151 if (file.isFile()) { | |
152 try { | |
153 return FileUtil.loadFileBytes(file); | |
154 } catch (IOException e) { | |
155 throw new VcsException("Failed to load content of file: " + file.getAbsolutePath(), e); | |
156 } | |
157 } else { | |
158 Loggers.VCS.warn("Unable to obtain content of the file: " + filePath); | |
12 | 159 } |
30 | 160 } finally { |
161 FileUtil.delete(parentDir); | |
12 | 162 } |
0 | 163 return new byte[0]; |
164 } | |
165 | |
86 | 166 @NotNull |
0 | 167 public String getName() { |
28
a7cab5083ada
libraries moved on top level, dummy implementation of agent side checkout interface
Pavel.Sher
parents:
27
diff
changeset
|
168 return Constants.VCS_NAME; |
0 | 169 } |
170 | |
86 | 171 @NotNull |
0 | 172 @Used("jsp") |
173 public String getDisplayName() { | |
174 return "Mercurial"; | |
175 } | |
176 | |
177 @Nullable | |
178 public PropertiesProcessor getVcsPropertiesProcessor() { | |
179 return new AbstractVcsPropertiesProcessor() { | |
180 public Collection<InvalidProperty> process(final Map<String, String> properties) { | |
181 List<InvalidProperty> result = new ArrayList<InvalidProperty>(); | |
182 if (isEmpty(properties.get(Constants.HG_COMMAND_PATH_PROP))) { | |
183 result.add(new InvalidProperty(Constants.HG_COMMAND_PATH_PROP, "Path to 'hg' command must be specified")); | |
184 } | |
185 if (isEmpty(properties.get(Constants.REPOSITORY_PROP))) { | |
186 result.add(new InvalidProperty(Constants.REPOSITORY_PROP, "Repository must be specified")); | |
187 } | |
62
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
188 if (isEmpty(properties.get(Constants.SERVER_CLONE_PATH_PROP))) { |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
189 properties.put(Constants.SERVER_CLONE_PATH_PROP, myDefaultWorkFolderParent.getAbsolutePath()); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
190 } |
0 | 191 return result; |
192 } | |
193 }; | |
194 } | |
195 | |
86 | 196 @NotNull |
0 | 197 public String getVcsSettingsJspFilePath() { |
198 return "mercurialSettings.jsp"; | |
199 } | |
200 | |
201 @NotNull | |
86 | 202 public String getCurrentVersion(@NotNull final VcsRoot root) throws VcsException { |
19 | 203 // we will return full version of the most recent change as current version |
44 | 204 syncClonedRepository(root); |
205 Settings settings = createSettings(root); | |
57 | 206 BranchesCommand branches = new BranchesCommand(settings); |
207 Map<String, ChangeSet> result = branches.execute(); | |
208 if (!result.containsKey(settings.getBranchName())) { | |
209 throw new VcsException("Unable to find current version for the branch: " + settings.getBranchName()); | |
210 } | |
211 | |
212 return result.get(settings.getBranchName()).getFullVersion(); | |
0 | 213 } |
214 | |
106 | 215 public boolean sourcesUpdatePossibleIfChangesNotFound(@NotNull final VcsRoot root) { |
216 return false; | |
217 } | |
218 | |
86 | 219 @NotNull |
0 | 220 public String describeVcsRoot(final VcsRoot vcsRoot) { |
221 return "mercurial: " + vcsRoot.getProperty(Constants.REPOSITORY_PROP); | |
222 } | |
223 | |
106 | 224 @Override |
225 public TestConnectionSupport getTestConnectionSupport() { | |
226 return new TestConnectionSupport() { | |
227 public String testConnection(@NotNull final VcsRoot vcsRoot) throws VcsException { | |
228 Settings settings = createSettings(vcsRoot); | |
229 IdentifyCommand id = new IdentifyCommand(settings); | |
230 StringBuilder res = new StringBuilder(); | |
231 res.append(quoteIfNeeded(settings.getHgCommandPath())); | |
232 res.append(" identify "); | |
233 final String obfuscatedUrl = CommandUtil.removePrivateData(settings.getRepositoryUrl(), Collections.singleton(settings.getPassword())); | |
234 res.append(quoteIfNeeded(obfuscatedUrl)); | |
235 res.append('\n').append(id.execute()); | |
236 return res.toString(); | |
237 } | |
238 }; | |
0 | 239 } |
240 | |
17 | 241 private String quoteIfNeeded(@NotNull String str) { |
242 if (str.indexOf(' ') != -1) { | |
243 return "\"" + str + "\""; | |
244 } | |
245 | |
246 return str; | |
247 } | |
248 | |
0 | 249 @Nullable |
250 public Map<String, String> getDefaultVcsProperties() { | |
62
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
251 Map<String, String> defaults = new HashMap<String, String>(); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
252 defaults.put(Constants.HG_COMMAND_PATH_PROP, "hg"); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
253 defaults.put(Constants.SERVER_CLONE_PATH_PROP, myDefaultWorkFolderParent.getAbsolutePath()); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
254 return defaults; |
0 | 255 } |
256 | |
86 | 257 public String getVersionDisplayName(@NotNull final String version, @NotNull final VcsRoot root) throws VcsException { |
59
30cc10fe9479
accept version in two formats: with rev number and without
Pavel.Sher
parents:
58
diff
changeset
|
258 return new ChangeSet(version).getId(); |
0 | 259 } |
260 | |
261 @NotNull | |
262 public Comparator<String> getVersionComparator() { | |
19 | 263 // comparator is called when TeamCity needs to sort modifications in the order of their appearance, |
264 // currently we sort changes by revision number, not sure however that this is a good idea, | |
265 // probably it would be better to sort them by timestamp (and to add timestamp into the version). | |
0 | 266 return new Comparator<String>() { |
267 public int compare(final String o1, final String o2) { | |
268 try { | |
269 return new ChangeSet(o1).getRevNumber() - new ChangeSet(o2).getRevNumber(); | |
270 } catch (Exception e) { | |
271 return 1; | |
272 } | |
273 } | |
274 }; | |
275 } | |
276 | |
19 | 277 // builds patch from version to version |
79 | 278 private void buildIncrementalPatch(final Settings settings, @NotNull final ChangeSet fromVer, @NotNull final ChangeSet toVer, final PatchBuilder builder, final CheckoutRules checkoutRules) |
0 | 279 throws VcsException, IOException { |
280 StatusCommand st = new StatusCommand(settings); | |
281 st.setFromRevId(fromVer.getId()); | |
282 st.setToRevId(toVer.getId()); | |
283 List<ModifiedFile> modifiedFiles = st.execute(); | |
284 List<String> notDeletedFiles = new ArrayList<String>(); | |
285 for (ModifiedFile f: modifiedFiles) { | |
286 if (f.getStatus() != ModifiedFile.Status.REMOVED) { | |
287 notDeletedFiles.add(f.getPath()); | |
288 } | |
289 } | |
290 | |
47
c785bc4c5f39
minor fix: do not call cat command if there are no files to export
Pavel.Sher
parents:
46
diff
changeset
|
291 if (notDeletedFiles.isEmpty()) return; |
c785bc4c5f39
minor fix: do not call cat command if there are no files to export
Pavel.Sher
parents:
46
diff
changeset
|
292 |
0 | 293 CatCommand cc = new CatCommand(settings); |
294 cc.setRevId(toVer.getId()); | |
295 File parentDir = cc.execute(notDeletedFiles); | |
296 | |
297 try { | |
298 for (ModifiedFile f: modifiedFiles) { | |
79 | 299 String mappedPath = checkoutRules.map(f.getPath()); |
300 if (mappedPath == null) continue; // skip | |
301 final File virtualFile = new File(mappedPath); | |
0 | 302 if (f.getStatus() == ModifiedFile.Status.REMOVED) { |
303 builder.deleteFile(virtualFile, true); | |
304 } else { | |
305 File realFile = new File(parentDir, f.getPath()); | |
306 FileInputStream is = new FileInputStream(realFile); | |
307 try { | |
11 | 308 builder.changeOrCreateBinaryFile(virtualFile, null, is, realFile.length()); |
0 | 309 } finally { |
310 is.close(); | |
311 } | |
312 } | |
313 } | |
314 } finally { | |
315 FileUtil.delete(parentDir); | |
316 } | |
317 } | |
318 | |
19 | 319 // builds patch by exporting files using specified version |
79 | 320 private void buildFullPatch(final Settings settings, @NotNull final ChangeSet toVer, final PatchBuilder builder, final CheckoutRules checkoutRules) |
0 | 321 throws IOException, VcsException { |
322 CloneCommand cl = new CloneCommand(settings); | |
57 | 323 // clone from the local repository |
67
e6971dc6b17c
always use url with credentials if username/password are specified
Pavel.Sher
parents:
62
diff
changeset
|
324 cl.setRepository(settings.getLocalRepositoryDir().getAbsolutePath()); |
0 | 325 cl.setToId(toVer.getId()); |
57 | 326 cl.setUpdateWorkingDir(false); |
0 | 327 File tempDir = FileUtil.createTempDirectory("mercurial", toVer.getId()); |
328 try { | |
329 final File repRoot = new File(tempDir, "rep"); | |
330 cl.setDestDir(repRoot.getAbsolutePath()); | |
331 cl.execute(); | |
57 | 332 |
333 UpdateCommand up = new UpdateCommand(settings); | |
334 up.setWorkDirectory(repRoot.getAbsolutePath()); | |
335 up.setToId(toVer.getId()); | |
336 up.execute(); | |
337 | |
0 | 338 buildPatchFromDirectory(builder, repRoot, new FileFilter() { |
339 public boolean accept(final File file) { | |
340 return !(file.isDirectory() && ".hg".equals(file.getName())); | |
341 } | |
79 | 342 }, checkoutRules); |
0 | 343 } finally { |
344 FileUtil.delete(tempDir); | |
345 } | |
346 } | |
347 | |
79 | 348 private void buildPatchFromDirectory(final PatchBuilder builder, final File repRoot, final FileFilter filter, final CheckoutRules checkoutRules) throws IOException { |
349 buildPatchFromDirectory(repRoot, builder, repRoot, filter, checkoutRules); | |
0 | 350 } |
351 | |
79 | 352 private void buildPatchFromDirectory(File curDir, final PatchBuilder builder, final File repRoot, final FileFilter filter, final CheckoutRules checkoutRules) throws IOException { |
0 | 353 File[] files = curDir.listFiles(filter); |
354 if (files != null) { | |
355 for (File realFile: files) { | |
356 String relPath = realFile.getAbsolutePath().substring(repRoot.getAbsolutePath().length()); | |
79 | 357 String mappedPath = checkoutRules.map(relPath); |
97 | 358 if (mappedPath != null && mappedPath.length() > 0) { |
79 | 359 final File virtualFile = new File(mappedPath); |
360 if (realFile.isDirectory()) { | |
361 builder.createDirectory(virtualFile); | |
362 buildPatchFromDirectory(realFile, builder, repRoot, filter, checkoutRules); | |
363 } else { | |
364 final FileInputStream is = new FileInputStream(realFile); | |
365 try { | |
366 builder.createBinaryFile(virtualFile, null, is, realFile.length()); | |
367 } finally { | |
368 is.close(); | |
369 } | |
0 | 370 } |
97 | 371 } else { |
372 if (realFile.isDirectory()) { | |
373 buildPatchFromDirectory(realFile, builder, repRoot, filter, checkoutRules); | |
374 } | |
0 | 375 } |
376 } | |
377 } | |
378 } | |
379 | |
19 | 380 // updates current working copy of repository by pulling changes from the repository specified in VCS root |
44 | 381 private void syncClonedRepository(final VcsRoot root) throws VcsException { |
382 Settings settings = createSettings(root); | |
57 | 383 File workDir = settings.getLocalRepositoryDir(); |
21 | 384 lockWorkDir(workDir); |
385 try { | |
29 | 386 if (settings.hasCopyOfRepository()) { |
0 | 387 // update |
8 | 388 PullCommand pull = new PullCommand(settings); |
389 pull.execute(); | |
0 | 390 } else { |
391 // clone | |
392 CloneCommand cl = new CloneCommand(settings); | |
22 | 393 cl.setDestDir(workDir.getAbsolutePath()); |
18 | 394 cl.setUpdateWorkingDir(false); |
0 | 395 cl.execute(); |
396 } | |
21 | 397 } finally { |
398 unlockWorkDir(workDir); | |
0 | 399 } |
400 } | |
401 | |
44 | 402 @Override |
403 public LabelingSupport getLabelingSupport() { | |
404 return this; | |
405 } | |
406 | |
106 | 407 @NotNull |
408 public VcsFileContentProvider getContentProvider() { | |
409 return this; | |
410 } | |
411 | |
412 @NotNull | |
413 public CollectChangesPolicy getCollectChangesPolicy() { | |
414 return new CollectChangesByIncludeRules() { | |
415 @NotNull | |
416 public IncludeRuleChangeCollector getChangeCollector(@NotNull final VcsRoot root, @NotNull final String fromVersion, @Nullable final String currentVersion) throws VcsException { | |
417 return new IncludeRuleChangeCollector() { | |
418 @NotNull | |
419 public List<ModificationData> collectChanges(@NotNull final IncludeRule includeRule) throws VcsException { | |
420 syncClonedRepository(root); | |
421 | |
422 // first obtain changes between specified versions | |
423 List<ModificationData> result = new ArrayList<ModificationData>(); | |
424 if (currentVersion == null) return result; | |
425 | |
426 Settings settings = createSettings(root); | |
427 LogCommand lc = new LogCommand(settings); | |
428 String fromId = new ChangeSetRevision(fromVersion).getId(); | |
429 lc.setFromRevId(fromId); | |
430 lc.setToRevId(new ChangeSetRevision(currentVersion).getId()); | |
431 List<ChangeSet> changeSets = lc.execute(); | |
432 if (changeSets.isEmpty()) { | |
433 return result; | |
434 } | |
435 | |
436 // invoke status command for each changeset and determine what files were modified in these changesets | |
437 StatusCommand st = new StatusCommand(settings); | |
438 ChangeSet prev = new ChangeSet(fromVersion); | |
439 for (ChangeSet cur : changeSets) { | |
440 if (cur.getId().equals(fromId)) continue; // skip already reported changeset | |
441 | |
442 String prevId = prev.getId(); | |
443 List<ChangeSetRevision> curParents = cur.getParents(); | |
444 boolean merge = curParents != null && curParents.size() > 1; | |
445 if (curParents != null && !merge) { | |
446 prevId = curParents.get(0).getId(); | |
447 } | |
448 | |
449 List<ModifiedFile> modifiedFiles = new ArrayList<ModifiedFile>(); | |
450 if (merge) { | |
451 modifiedFiles.addAll(computeModifiedFilesForMergeCommit(settings, cur)); | |
452 } else { | |
453 st.setFromRevId(prevId); | |
454 st.setToRevId(cur.getId()); | |
455 modifiedFiles = st.execute(); | |
456 } | |
457 | |
458 // changeset full version will be set into VcsChange structure and | |
459 // stored in database (note that getContent method will be invoked with this version) | |
460 List<VcsChange> files = toVcsChanges(modifiedFiles, prev.getFullVersion(), cur.getFullVersion(), includeRule); | |
461 if (files.isEmpty() && !merge) continue; | |
462 ModificationData md = new ModificationData(cur.getTimestamp(), files, cur.getDescription(), cur.getUser(), root, cur.getFullVersion(), cur.getId()); | |
463 if (merge) { | |
464 md.setCanBeIgnored(false); | |
465 } | |
466 result.add(md); | |
467 prev = cur; | |
468 } | |
469 | |
470 return result; | |
471 } | |
472 | |
473 public void dispose() throws VcsException { | |
474 } | |
475 }; | |
476 } | |
477 }; | |
478 } | |
479 | |
480 @NotNull | |
481 public BuildPatchPolicy getBuildPatchPolicy() { | |
482 return new BuildPatchByCheckoutRules() { | |
483 public void buildPatch(@NotNull final VcsRoot root, | |
484 @Nullable final String fromVersion, | |
485 @NotNull final String toVersion, | |
486 @NotNull final PatchBuilder builder, | |
487 @NotNull final CheckoutRules checkoutRules) throws IOException, VcsException { | |
488 syncClonedRepository(root); | |
489 Settings settings = createSettings(root); | |
490 if (fromVersion == null) { | |
491 buildFullPatch(settings, new ChangeSet(toVersion), builder, checkoutRules); | |
492 } else { | |
493 buildIncrementalPatch(settings, new ChangeSet(fromVersion), new ChangeSet(toVersion), builder, checkoutRules); | |
494 } | |
495 } | |
496 }; | |
497 } | |
498 | |
22 | 499 private void lockWorkDir(@NotNull File workDir) { |
21 | 500 getWorkDirLock(workDir).lock(); |
501 } | |
502 | |
22 | 503 private void unlockWorkDir(@NotNull File workDir) { |
21 | 504 getWorkDirLock(workDir).unlock(); |
505 } | |
506 | |
31 | 507 @Override |
106 | 508 public boolean allowSourceCaching() { |
31 | 509 // since a copy of repository for each VCS root is already stored on disk |
106 | 510 // we do not need separate cache for our patches |
511 return false; | |
31 | 512 } |
513 | |
22 | 514 private Lock getWorkDirLock(final File workDir) { |
515 String path = workDir.getAbsolutePath(); | |
516 Lock lock = myWorkDirLocks.get(path); | |
21 | 517 if (lock == null) { |
518 lock = new ReentrantLock(); | |
22 | 519 Lock curLock = myWorkDirLocks.putIfAbsent(path, lock); |
21 | 520 if (curLock != null) { |
521 lock = curLock; | |
522 } | |
523 } | |
524 return lock; | |
525 } | |
526 | |
23 | 527 private void removeOldWorkFolders() { |
103 | 528 Set<File> workDirs = getAllClonedRepos(); |
529 if (workDirs == null) return; | |
530 | |
531 for (VcsRoot vcsRoot: getMercurialVcsRoots()) { | |
532 try { | |
533 Settings s = createSettings(vcsRoot); | |
534 workDirs.remove(PathUtil.getCanonicalFile(s.getLocalRepositoryDir())); | |
535 } catch (VcsException e) { | |
536 Loggers.VCS.error(e); | |
537 } | |
538 } | |
539 | |
540 for (File f: workDirs) { | |
541 lockWorkDir(f); | |
542 try { | |
543 FileUtil.delete(f); | |
544 } finally { | |
545 unlockWorkDir(f); | |
546 } | |
547 } | |
548 } | |
549 | |
550 private Collection<VcsRoot> getMercurialVcsRoots() { | |
551 List<VcsRoot> res = new ArrayList<VcsRoot>(myVcsManager.getAllRegisteredVcsRoots()); | |
552 FilterUtil.filterCollection(res, new Filter<VcsRoot>() { | |
553 public boolean accept(@NotNull final VcsRoot data) { | |
554 return getName().equals(data.getVcsName()); | |
555 } | |
556 }); | |
557 return res; | |
558 } | |
559 | |
560 @Nullable | |
561 private Set<File> getAllClonedRepos() { | |
62
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
562 File workFoldersParent = myDefaultWorkFolderParent; |
103 | 563 if (!workFoldersParent.isDirectory()) return null; |
23 | 564 |
565 Set<File> workDirs = new HashSet<File>(); | |
566 File[] files = workFoldersParent.listFiles(new FileFilter() { | |
567 public boolean accept(final File file) { | |
568 return file.isDirectory() && file.getName().startsWith(Settings.DEFAULT_WORK_DIR_PREFIX); | |
569 } | |
570 }); | |
571 if (files != null) { | |
572 for (File f: files) { | |
46 | 573 workDirs.add(PathUtil.getCanonicalFile(f)); |
23 | 574 } |
575 } | |
103 | 576 return workDirs; |
23 | 577 } |
44 | 578 |
579 public String label(@NotNull String label, @NotNull String version, @NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules) throws VcsException { | |
580 syncClonedRepository(root); | |
581 | |
582 Settings settings = createSettings(root); | |
583 | |
584 // I do not know why but hg tag does not work correctly if | |
585 // update command was not invoked for the current repo | |
586 // in such case if there were no tags before Mercurial attempts to | |
587 // create new head when tag is pushed to the parent repository | |
588 UpdateCommand uc = new UpdateCommand(settings); | |
589 uc.execute(); | |
590 | |
591 String fixedTagname = fixTagName(label); | |
592 TagCommand tc = new TagCommand(settings); | |
593 tc.setRevId(new ChangeSet(version).getId()); | |
594 tc.setTag(fixedTagname); | |
595 tc.execute(); | |
596 | |
597 PushCommand pc = new PushCommand(settings); | |
101 | 598 // pc.setForce(true); |
44 | 599 pc.execute(); |
600 return fixedTagname; | |
601 } | |
602 | |
603 private String fixTagName(final String label) { | |
604 // according to Mercurial documentation http://hgbook.red-bean.com/hgbookch8.html#x12-1570008 | |
605 // tag name must not contain: | |
606 // Colon (ASCII 58, “:”) | |
607 // Carriage return (ASCII 13, “\r”) | |
608 // Newline (ASCII 10, “\n”) | |
609 // all these characters will be replaced with _ (underscore) | |
610 return label.replace(':', '_').replace('\r', '_').replace('\n', '_'); | |
611 } | |
612 | |
62
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
613 private Settings createSettings(final VcsRoot root) throws VcsException { |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
614 Settings settings = new Settings(myDefaultWorkFolderParent, root); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
615 String customClonePath = root.getProperty(Constants.SERVER_CLONE_PATH_PROP); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
616 if (!StringUtil.isEmptyOrSpaces(customClonePath) && !myDefaultWorkFolderParent.equals(new File(customClonePath).getAbsoluteFile())) { |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
617 File parentDir = new File(customClonePath); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
618 createClonedRepositoryParentDir(parentDir); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
619 |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
620 // take last part of repository path |
67
e6971dc6b17c
always use url with credentials if username/password are specified
Pavel.Sher
parents:
62
diff
changeset
|
621 String repPath = settings.getRepositoryUrl(); |
62
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
622 String[] splitted = repPath.split("[/\\\\]"); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
623 if (splitted.length > 0) { |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
624 repPath = splitted[splitted.length-1]; |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
625 } |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
626 |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
627 File customWorkingDir = new File(parentDir, repPath); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
628 settings.setWorkingDir(customWorkingDir); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
629 } else { |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
630 createClonedRepositoryParentDir(myDefaultWorkFolderParent); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
631 } |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
632 return settings; |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
633 } |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
634 |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
635 private void createClonedRepositoryParentDir(final File parentDir) throws VcsException { |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
636 if (!parentDir.exists() && !parentDir.mkdirs()) { |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
637 throw new VcsException("Failed to create parent directory for cloned repository: " + parentDir.getAbsolutePath()); |
b328c6b6526d
TW-5636: Mercurial plugin can easilly hit Windows MAX_PATH limitations
Pavel.Sher
parents:
59
diff
changeset
|
638 } |
44 | 639 } |
48 | 640 |
641 public boolean isAgentSideCheckoutAvailable() { | |
642 return true; | |
643 } | |
0 | 644 } |