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