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