##// END OF EJS Templates
convert: reenable SVN support after license issue solved
Patrick Mezard -
r9819:dec17728 default
parent child Browse files
Show More
@@ -1,1143 +1,1142 b''
1 1 # Subversion 1.4/1.5 Python API backend
2 2 #
3 3 # Copyright(C) 2007 Daniel Holth et al
4 4
5 5 import os
6 6 import re
7 7 import sys
8 8 import cPickle as pickle
9 9 import tempfile
10 10 import urllib
11 11
12 12 from mercurial import strutil, util, encoding
13 13 from mercurial.i18n import _
14 14
15 15 # Subversion stuff. Works best with very recent Python SVN bindings
16 16 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
17 17 # these bindings.
18 18
19 19 from cStringIO import StringIO
20 20
21 21 from common import NoRepo, MissingTool, commit, encodeargs, decodeargs
22 22 from common import commandline, converter_source, converter_sink, mapfile
23 23
24 24 try:
25 raise ImportError("SVN support disabled due to license incompatibility")
26 25 from svn.core import SubversionException, Pool
27 26 import svn
28 27 import svn.client
29 28 import svn.core
30 29 import svn.ra
31 30 import svn.delta
32 31 import transport
33 32 import warnings
34 33 warnings.filterwarnings('ignore',
35 34 module='svn.core',
36 35 category=DeprecationWarning)
37 36
38 37 except ImportError:
39 38 pass
40 39
41 40 class SvnPathNotFound(Exception):
42 41 pass
43 42
44 43 def geturl(path):
45 44 try:
46 45 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
47 46 except SubversionException:
48 47 pass
49 48 if os.path.isdir(path):
50 49 path = os.path.normpath(os.path.abspath(path))
51 50 if os.name == 'nt':
52 51 path = '/' + util.normpath(path)
53 52 # Module URL is later compared with the repository URL returned
54 53 # by svn API, which is UTF-8.
55 54 path = encoding.tolocal(path)
56 55 return 'file://%s' % urllib.quote(path)
57 56 return path
58 57
59 58 def optrev(number):
60 59 optrev = svn.core.svn_opt_revision_t()
61 60 optrev.kind = svn.core.svn_opt_revision_number
62 61 optrev.value.number = number
63 62 return optrev
64 63
65 64 class changedpath(object):
66 65 def __init__(self, p):
67 66 self.copyfrom_path = p.copyfrom_path
68 67 self.copyfrom_rev = p.copyfrom_rev
69 68 self.action = p.action
70 69
71 70 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
72 71 strict_node_history=False):
73 72 protocol = -1
74 73 def receiver(orig_paths, revnum, author, date, message, pool):
75 74 if orig_paths is not None:
76 75 for k, v in orig_paths.iteritems():
77 76 orig_paths[k] = changedpath(v)
78 77 pickle.dump((orig_paths, revnum, author, date, message),
79 78 fp, protocol)
80 79
81 80 try:
82 81 # Use an ra of our own so that our parent can consume
83 82 # our results without confusing the server.
84 83 t = transport.SvnRaTransport(url=url)
85 84 svn.ra.get_log(t.ra, paths, start, end, limit,
86 85 discover_changed_paths,
87 86 strict_node_history,
88 87 receiver)
89 88 except SubversionException, (inst, num):
90 89 pickle.dump(num, fp, protocol)
91 90 except IOError:
92 91 # Caller may interrupt the iteration
93 92 pickle.dump(None, fp, protocol)
94 93 else:
95 94 pickle.dump(None, fp, protocol)
96 95 fp.close()
97 96 # With large history, cleanup process goes crazy and suddenly
98 97 # consumes *huge* amount of memory. The output file being closed,
99 98 # there is no need for clean termination.
100 99 os._exit(0)
101 100
102 101 def debugsvnlog(ui, **opts):
103 102 """Fetch SVN log in a subprocess and channel them back to parent to
104 103 avoid memory collection issues.
105 104 """
106 105 util.set_binary(sys.stdin)
107 106 util.set_binary(sys.stdout)
108 107 args = decodeargs(sys.stdin.read())
109 108 get_log_child(sys.stdout, *args)
110 109
111 110 class logstream(object):
112 111 """Interruptible revision log iterator."""
113 112 def __init__(self, stdout):
114 113 self._stdout = stdout
115 114
116 115 def __iter__(self):
117 116 while True:
118 117 try:
119 118 entry = pickle.load(self._stdout)
120 119 except EOFError:
121 120 raise util.Abort(_('Mercurial failed to run itself, check'
122 121 ' hg executable is in PATH'))
123 122 try:
124 123 orig_paths, revnum, author, date, message = entry
125 124 except:
126 125 if entry is None:
127 126 break
128 127 raise SubversionException("child raised exception", entry)
129 128 yield entry
130 129
131 130 def close(self):
132 131 if self._stdout:
133 132 self._stdout.close()
134 133 self._stdout = None
135 134
136 135
137 136 # Check to see if the given path is a local Subversion repo. Verify this by
138 137 # looking for several svn-specific files and directories in the given
139 138 # directory.
140 139 def filecheck(path, proto):
141 140 for x in ('locks', 'hooks', 'format', 'db', ):
142 141 if not os.path.exists(os.path.join(path, x)):
143 142 return False
144 143 return True
145 144
146 145 # Check to see if a given path is the root of an svn repo over http. We verify
147 146 # this by requesting a version-controlled URL we know can't exist and looking
148 147 # for the svn-specific "not found" XML.
149 148 def httpcheck(path, proto):
150 149 return ('<m:human-readable errcode="160013">' in
151 150 urllib.urlopen('%s://%s/!svn/ver/0/.svn' % (proto, path)).read())
152 151
153 152 protomap = {'http': httpcheck,
154 153 'https': httpcheck,
155 154 'file': filecheck,
156 155 }
157 156 def issvnurl(url):
158 157 try:
159 158 proto, path = url.split('://', 1)
160 159 if proto == 'file':
161 160 path = urllib.url2pathname(path)
162 161 except ValueError:
163 162 proto = 'file'
164 163 path = os.path.abspath(url)
165 164 if proto == 'file':
166 165 path = path.replace(os.sep, '/')
167 166 check = protomap.get(proto, lambda p, p2: False)
168 167 while '/' in path:
169 168 if check(path, proto):
170 169 return True
171 170 path = path.rsplit('/', 1)[0]
172 171 return False
173 172
174 173 # SVN conversion code stolen from bzr-svn and tailor
175 174 #
176 175 # Subversion looks like a versioned filesystem, branches structures
177 176 # are defined by conventions and not enforced by the tool. First,
178 177 # we define the potential branches (modules) as "trunk" and "branches"
179 178 # children directories. Revisions are then identified by their
180 179 # module and revision number (and a repository identifier).
181 180 #
182 181 # The revision graph is really a tree (or a forest). By default, a
183 182 # revision parent is the previous revision in the same module. If the
184 183 # module directory is copied/moved from another module then the
185 184 # revision is the module root and its parent the source revision in
186 185 # the parent module. A revision has at most one parent.
187 186 #
188 187 class svn_source(converter_source):
189 188 def __init__(self, ui, url, rev=None):
190 189 super(svn_source, self).__init__(ui, url, rev=rev)
191 190
192 191 if not (url.startswith('svn://') or url.startswith('svn+ssh://') or
193 192 (os.path.exists(url) and
194 193 os.path.exists(os.path.join(url, '.svn'))) or
195 194 issvnurl(url)):
196 195 raise NoRepo("%s does not look like a Subversion repo" % url)
197 196
198 197 try:
199 198 SubversionException
200 199 except NameError:
201 200 raise MissingTool(_('Subversion python bindings could not be loaded'))
202 201
203 202 try:
204 203 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
205 204 if version < (1, 4):
206 205 raise MissingTool(_('Subversion python bindings %d.%d found, '
207 206 '1.4 or later required') % version)
208 207 except AttributeError:
209 208 raise MissingTool(_('Subversion python bindings are too old, 1.4 '
210 209 'or later required'))
211 210
212 211 self.lastrevs = {}
213 212
214 213 latest = None
215 214 try:
216 215 # Support file://path@rev syntax. Useful e.g. to convert
217 216 # deleted branches.
218 217 at = url.rfind('@')
219 218 if at >= 0:
220 219 latest = int(url[at+1:])
221 220 url = url[:at]
222 221 except ValueError:
223 222 pass
224 223 self.url = geturl(url)
225 224 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
226 225 try:
227 226 self.transport = transport.SvnRaTransport(url=self.url)
228 227 self.ra = self.transport.ra
229 228 self.ctx = self.transport.client
230 229 self.baseurl = svn.ra.get_repos_root(self.ra)
231 230 # Module is either empty or a repository path starting with
232 231 # a slash and not ending with a slash.
233 232 self.module = urllib.unquote(self.url[len(self.baseurl):])
234 233 self.prevmodule = None
235 234 self.rootmodule = self.module
236 235 self.commits = {}
237 236 self.paths = {}
238 237 self.uuid = svn.ra.get_uuid(self.ra)
239 238 except SubversionException:
240 239 ui.traceback()
241 240 raise NoRepo("%s does not look like a Subversion repo" % self.url)
242 241
243 242 if rev:
244 243 try:
245 244 latest = int(rev)
246 245 except ValueError:
247 246 raise util.Abort(_('svn: revision %s is not an integer') % rev)
248 247
249 248 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
250 249 try:
251 250 self.startrev = int(self.startrev)
252 251 if self.startrev < 0:
253 252 self.startrev = 0
254 253 except ValueError:
255 254 raise util.Abort(_('svn: start revision %s is not an integer')
256 255 % self.startrev)
257 256
258 257 self.head = self.latest(self.module, latest)
259 258 if not self.head:
260 259 raise util.Abort(_('no revision found in module %s')
261 260 % self.module)
262 261 self.last_changed = self.revnum(self.head)
263 262
264 263 self._changescache = None
265 264
266 265 if os.path.exists(os.path.join(url, '.svn/entries')):
267 266 self.wc = url
268 267 else:
269 268 self.wc = None
270 269 self.convertfp = None
271 270
272 271 def setrevmap(self, revmap):
273 272 lastrevs = {}
274 273 for revid in revmap.iterkeys():
275 274 uuid, module, revnum = self.revsplit(revid)
276 275 lastrevnum = lastrevs.setdefault(module, revnum)
277 276 if revnum > lastrevnum:
278 277 lastrevs[module] = revnum
279 278 self.lastrevs = lastrevs
280 279
281 280 def exists(self, path, optrev):
282 281 try:
283 282 svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path),
284 283 optrev, False, self.ctx)
285 284 return True
286 285 except SubversionException:
287 286 return False
288 287
289 288 def getheads(self):
290 289
291 290 def isdir(path, revnum):
292 291 kind = self._checkpath(path, revnum)
293 292 return kind == svn.core.svn_node_dir
294 293
295 294 def getcfgpath(name, rev):
296 295 cfgpath = self.ui.config('convert', 'svn.' + name)
297 296 if cfgpath is not None and cfgpath.strip() == '':
298 297 return None
299 298 path = (cfgpath or name).strip('/')
300 299 if not self.exists(path, rev):
301 300 if cfgpath:
302 301 raise util.Abort(_('expected %s to be at %r, but not found')
303 302 % (name, path))
304 303 return None
305 304 self.ui.note(_('found %s at %r\n') % (name, path))
306 305 return path
307 306
308 307 rev = optrev(self.last_changed)
309 308 oldmodule = ''
310 309 trunk = getcfgpath('trunk', rev)
311 310 self.tags = getcfgpath('tags', rev)
312 311 branches = getcfgpath('branches', rev)
313 312
314 313 # If the project has a trunk or branches, we will extract heads
315 314 # from them. We keep the project root otherwise.
316 315 if trunk:
317 316 oldmodule = self.module or ''
318 317 self.module += '/' + trunk
319 318 self.head = self.latest(self.module, self.last_changed)
320 319 if not self.head:
321 320 raise util.Abort(_('no revision found in module %s')
322 321 % self.module)
323 322
324 323 # First head in the list is the module's head
325 324 self.heads = [self.head]
326 325 if self.tags is not None:
327 326 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
328 327
329 328 # Check if branches bring a few more heads to the list
330 329 if branches:
331 330 rpath = self.url.strip('/')
332 331 branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches),
333 332 rev, False, self.ctx)
334 333 for branch in branchnames.keys():
335 334 module = '%s/%s/%s' % (oldmodule, branches, branch)
336 335 if not isdir(module, self.last_changed):
337 336 continue
338 337 brevid = self.latest(module, self.last_changed)
339 338 if not brevid:
340 339 self.ui.note(_('ignoring empty branch %s\n') % branch)
341 340 continue
342 341 self.ui.note(_('found branch %s at %d\n') %
343 342 (branch, self.revnum(brevid)))
344 343 self.heads.append(brevid)
345 344
346 345 if self.startrev and self.heads:
347 346 if len(self.heads) > 1:
348 347 raise util.Abort(_('svn: start revision is not supported '
349 348 'with more than one branch'))
350 349 revnum = self.revnum(self.heads[0])
351 350 if revnum < self.startrev:
352 351 raise util.Abort(_('svn: no revision found after start revision %d')
353 352 % self.startrev)
354 353
355 354 return self.heads
356 355
357 356 def getfile(self, file, rev):
358 357 data, mode = self._getfile(file, rev)
359 358 self.modecache[(file, rev)] = mode
360 359 return data
361 360
362 361 def getmode(self, file, rev):
363 362 return self.modecache[(file, rev)]
364 363
365 364 def getchanges(self, rev):
366 365 if self._changescache and self._changescache[0] == rev:
367 366 return self._changescache[1]
368 367 self._changescache = None
369 368 self.modecache = {}
370 369 (paths, parents) = self.paths[rev]
371 370 if parents:
372 371 files, copies = self.expandpaths(rev, paths, parents)
373 372 else:
374 373 # Perform a full checkout on roots
375 374 uuid, module, revnum = self.revsplit(rev)
376 375 entries = svn.client.ls(self.baseurl + urllib.quote(module),
377 376 optrev(revnum), True, self.ctx)
378 377 files = [n for n,e in entries.iteritems()
379 378 if e.kind == svn.core.svn_node_file]
380 379 copies = {}
381 380
382 381 files.sort()
383 382 files = zip(files, [rev] * len(files))
384 383
385 384 # caller caches the result, so free it here to release memory
386 385 del self.paths[rev]
387 386 return (files, copies)
388 387
389 388 def getchangedfiles(self, rev, i):
390 389 changes = self.getchanges(rev)
391 390 self._changescache = (rev, changes)
392 391 return [f[0] for f in changes[0]]
393 392
394 393 def getcommit(self, rev):
395 394 if rev not in self.commits:
396 395 uuid, module, revnum = self.revsplit(rev)
397 396 self.module = module
398 397 self.reparent(module)
399 398 # We assume that:
400 399 # - requests for revisions after "stop" come from the
401 400 # revision graph backward traversal. Cache all of them
402 401 # down to stop, they will be used eventually.
403 402 # - requests for revisions before "stop" come to get
404 403 # isolated branches parents. Just fetch what is needed.
405 404 stop = self.lastrevs.get(module, 0)
406 405 if revnum < stop:
407 406 stop = revnum + 1
408 407 self._fetch_revisions(revnum, stop)
409 408 commit = self.commits[rev]
410 409 # caller caches the result, so free it here to release memory
411 410 del self.commits[rev]
412 411 return commit
413 412
414 413 def gettags(self):
415 414 tags = {}
416 415 if self.tags is None:
417 416 return tags
418 417
419 418 # svn tags are just a convention, project branches left in a
420 419 # 'tags' directory. There is no other relationship than
421 420 # ancestry, which is expensive to discover and makes them hard
422 421 # to update incrementally. Worse, past revisions may be
423 422 # referenced by tags far away in the future, requiring a deep
424 423 # history traversal on every calculation. Current code
425 424 # performs a single backward traversal, tracking moves within
426 425 # the tags directory (tag renaming) and recording a new tag
427 426 # everytime a project is copied from outside the tags
428 427 # directory. It also lists deleted tags, this behaviour may
429 428 # change in the future.
430 429 pendings = []
431 430 tagspath = self.tags
432 431 start = svn.ra.get_latest_revnum(self.ra)
433 432 try:
434 433 for entry in self._getlog([self.tags], start, self.startrev):
435 434 origpaths, revnum, author, date, message = entry
436 435 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
437 436 in origpaths.iteritems() if e.copyfrom_path]
438 437 # Apply moves/copies from more specific to general
439 438 copies.sort(reverse=True)
440 439
441 440 srctagspath = tagspath
442 441 if copies and copies[-1][2] == tagspath:
443 442 # Track tags directory moves
444 443 srctagspath = copies.pop()[0]
445 444
446 445 for source, sourcerev, dest in copies:
447 446 if not dest.startswith(tagspath + '/'):
448 447 continue
449 448 for tag in pendings:
450 449 if tag[0].startswith(dest):
451 450 tagpath = source + tag[0][len(dest):]
452 451 tag[:2] = [tagpath, sourcerev]
453 452 break
454 453 else:
455 454 pendings.append([source, sourcerev, dest])
456 455
457 456 # Filter out tags with children coming from different
458 457 # parts of the repository like:
459 458 # /tags/tag.1 (from /trunk:10)
460 459 # /tags/tag.1/foo (from /branches/foo:12)
461 460 # Here/tags/tag.1 discarded as well as its children.
462 461 # It happens with tools like cvs2svn. Such tags cannot
463 462 # be represented in mercurial.
464 463 addeds = dict((p, e.copyfrom_path) for p, e
465 464 in origpaths.iteritems()
466 465 if e.action == 'A' and e.copyfrom_path)
467 466 badroots = set()
468 467 for destroot in addeds:
469 468 for source, sourcerev, dest in pendings:
470 469 if (not dest.startswith(destroot + '/')
471 470 or source.startswith(addeds[destroot] + '/')):
472 471 continue
473 472 badroots.add(destroot)
474 473 break
475 474
476 475 for badroot in badroots:
477 476 pendings = [p for p in pendings if p[2] != badroot
478 477 and not p[2].startswith(badroot + '/')]
479 478
480 479 # Tell tag renamings from tag creations
481 480 remainings = []
482 481 for source, sourcerev, dest in pendings:
483 482 tagname = dest.split('/')[-1]
484 483 if source.startswith(srctagspath):
485 484 remainings.append([source, sourcerev, tagname])
486 485 continue
487 486 if tagname in tags:
488 487 # Keep the latest tag value
489 488 continue
490 489 # From revision may be fake, get one with changes
491 490 try:
492 491 tagid = self.latest(source, sourcerev)
493 492 if tagid and tagname not in tags:
494 493 tags[tagname] = tagid
495 494 except SvnPathNotFound:
496 495 # It happens when we are following directories
497 496 # we assumed were copied with their parents
498 497 # but were really created in the tag
499 498 # directory.
500 499 pass
501 500 pendings = remainings
502 501 tagspath = srctagspath
503 502
504 503 except SubversionException:
505 504 self.ui.note(_('no tags found at revision %d\n') % start)
506 505 return tags
507 506
508 507 def converted(self, rev, destrev):
509 508 if not self.wc:
510 509 return
511 510 if self.convertfp is None:
512 511 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
513 512 'a')
514 513 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
515 514 self.convertfp.flush()
516 515
517 516 def revid(self, revnum, module=None):
518 517 return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
519 518
520 519 def revnum(self, rev):
521 520 return int(rev.split('@')[-1])
522 521
523 522 def revsplit(self, rev):
524 523 url, revnum = rev.rsplit('@', 1)
525 524 revnum = int(revnum)
526 525 parts = url.split('/', 1)
527 526 uuid = parts.pop(0)[4:]
528 527 mod = ''
529 528 if parts:
530 529 mod = '/' + parts[0]
531 530 return uuid, mod, revnum
532 531
533 532 def latest(self, path, stop=0):
534 533 """Find the latest revid affecting path, up to stop. It may return
535 534 a revision in a different module, since a branch may be moved without
536 535 a change being reported. Return None if computed module does not
537 536 belong to rootmodule subtree.
538 537 """
539 538 if not path.startswith(self.rootmodule):
540 539 # Requests on foreign branches may be forbidden at server level
541 540 self.ui.debug('ignoring foreign branch %r\n' % path)
542 541 return None
543 542
544 543 if not stop:
545 544 stop = svn.ra.get_latest_revnum(self.ra)
546 545 try:
547 546 prevmodule = self.reparent('')
548 547 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
549 548 self.reparent(prevmodule)
550 549 except SubversionException:
551 550 dirent = None
552 551 if not dirent:
553 552 raise SvnPathNotFound(_('%s not found up to revision %d') % (path, stop))
554 553
555 554 # stat() gives us the previous revision on this line of
556 555 # development, but it might be in *another module*. Fetch the
557 556 # log and detect renames down to the latest revision.
558 557 stream = self._getlog([path], stop, dirent.created_rev)
559 558 try:
560 559 for entry in stream:
561 560 paths, revnum, author, date, message = entry
562 561 if revnum <= dirent.created_rev:
563 562 break
564 563
565 564 for p in paths:
566 565 if not path.startswith(p) or not paths[p].copyfrom_path:
567 566 continue
568 567 newpath = paths[p].copyfrom_path + path[len(p):]
569 568 self.ui.debug("branch renamed from %s to %s at %d\n" %
570 569 (path, newpath, revnum))
571 570 path = newpath
572 571 break
573 572 finally:
574 573 stream.close()
575 574
576 575 if not path.startswith(self.rootmodule):
577 576 self.ui.debug('ignoring foreign branch %r\n' % path)
578 577 return None
579 578 return self.revid(dirent.created_rev, path)
580 579
581 580 def reparent(self, module):
582 581 """Reparent the svn transport and return the previous parent."""
583 582 if self.prevmodule == module:
584 583 return module
585 584 svnurl = self.baseurl + urllib.quote(module)
586 585 prevmodule = self.prevmodule
587 586 if prevmodule is None:
588 587 prevmodule = ''
589 588 self.ui.debug("reparent to %s\n" % svnurl)
590 589 svn.ra.reparent(self.ra, svnurl)
591 590 self.prevmodule = module
592 591 return prevmodule
593 592
594 593 def expandpaths(self, rev, paths, parents):
595 594 entries = []
596 595 # Map of entrypath, revision for finding source of deleted
597 596 # revisions.
598 597 copyfrom = {}
599 598 copies = {}
600 599
601 600 new_module, revnum = self.revsplit(rev)[1:]
602 601 if new_module != self.module:
603 602 self.module = new_module
604 603 self.reparent(self.module)
605 604
606 605 for path, ent in paths:
607 606 entrypath = self.getrelpath(path)
608 607
609 608 kind = self._checkpath(entrypath, revnum)
610 609 if kind == svn.core.svn_node_file:
611 610 entries.append(self.recode(entrypath))
612 611 if not ent.copyfrom_path or not parents:
613 612 continue
614 613 # Copy sources not in parent revisions cannot be
615 614 # represented, ignore their origin for now
616 615 pmodule, prevnum = self.revsplit(parents[0])[1:]
617 616 if ent.copyfrom_rev < prevnum:
618 617 continue
619 618 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
620 619 if not copyfrom_path:
621 620 continue
622 621 self.ui.debug("copied to %s from %s@%s\n" %
623 622 (entrypath, copyfrom_path, ent.copyfrom_rev))
624 623 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
625 624 elif kind == 0: # gone, but had better be a deleted *file*
626 625 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
627 626 pmodule, prevnum = self.revsplit(parents[0])[1:]
628 627 parentpath = pmodule + "/" + entrypath
629 628 self.ui.debug("entry %s\n" % parentpath)
630 629
631 630 # We can avoid the reparent calls if the module has
632 631 # not changed but it probably does not worth the pain.
633 632 prevmodule = self.reparent('')
634 633 fromkind = svn.ra.check_path(self.ra, parentpath.strip('/'), prevnum)
635 634 self.reparent(prevmodule)
636 635
637 636 if fromkind == svn.core.svn_node_file:
638 637 entries.append(self.recode(entrypath))
639 638 elif fromkind == svn.core.svn_node_dir:
640 639 if ent.action == 'C':
641 640 children = self._find_children(path, prevnum)
642 641 else:
643 642 oroot = parentpath.strip('/')
644 643 nroot = path.strip('/')
645 644 children = self._find_children(oroot, prevnum)
646 645 children = [s.replace(oroot,nroot) for s in children]
647 646
648 647 for child in children:
649 648 childpath = self.getrelpath("/" + child, pmodule)
650 649 if not childpath:
651 650 continue
652 651 if childpath in copies:
653 652 del copies[childpath]
654 653 entries.append(childpath)
655 654 else:
656 655 self.ui.debug('unknown path in revision %d: %s\n' % \
657 656 (revnum, path))
658 657 elif kind == svn.core.svn_node_dir:
659 658 # If the directory just had a prop change,
660 659 # then we shouldn't need to look for its children.
661 660 if ent.action == 'M':
662 661 continue
663 662
664 663 children = sorted(self._find_children(path, revnum))
665 664 for child in children:
666 665 # Can we move a child directory and its
667 666 # parent in the same commit? (probably can). Could
668 667 # cause problems if instead of revnum -1,
669 668 # we have to look in (copyfrom_path, revnum - 1)
670 669 entrypath = self.getrelpath("/" + child)
671 670 if entrypath:
672 671 # Need to filter out directories here...
673 672 kind = self._checkpath(entrypath, revnum)
674 673 if kind != svn.core.svn_node_dir:
675 674 entries.append(self.recode(entrypath))
676 675
677 676 # Handle directory copies
678 677 if not ent.copyfrom_path or not parents:
679 678 continue
680 679 # Copy sources not in parent revisions cannot be
681 680 # represented, ignore their origin for now
682 681 pmodule, prevnum = self.revsplit(parents[0])[1:]
683 682 if ent.copyfrom_rev < prevnum:
684 683 continue
685 684 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule)
686 685 if not copyfrompath:
687 686 continue
688 687 copyfrom[path] = ent
689 688 self.ui.debug("mark %s came from %s:%d\n"
690 689 % (path, copyfrompath, ent.copyfrom_rev))
691 690 children = self._find_children(ent.copyfrom_path, ent.copyfrom_rev)
692 691 children.sort()
693 692 for child in children:
694 693 entrypath = self.getrelpath("/" + child, pmodule)
695 694 if not entrypath:
696 695 continue
697 696 copytopath = path + entrypath[len(copyfrompath):]
698 697 copytopath = self.getrelpath(copytopath)
699 698 copies[self.recode(copytopath)] = self.recode(entrypath)
700 699
701 700 return (list(set(entries)), copies)
702 701
703 702 def _fetch_revisions(self, from_revnum, to_revnum):
704 703 if from_revnum < to_revnum:
705 704 from_revnum, to_revnum = to_revnum, from_revnum
706 705
707 706 self.child_cset = None
708 707
709 708 def parselogentry(orig_paths, revnum, author, date, message):
710 709 """Return the parsed commit object or None, and True if
711 710 the revision is a branch root.
712 711 """
713 712 self.ui.debug("parsing revision %d (%d changes)\n" %
714 713 (revnum, len(orig_paths)))
715 714
716 715 branched = False
717 716 rev = self.revid(revnum)
718 717 # branch log might return entries for a parent we already have
719 718
720 719 if rev in self.commits or revnum < to_revnum:
721 720 return None, branched
722 721
723 722 parents = []
724 723 # check whether this revision is the start of a branch or part
725 724 # of a branch renaming
726 725 orig_paths = sorted(orig_paths.iteritems())
727 726 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
728 727 if root_paths:
729 728 path, ent = root_paths[-1]
730 729 if ent.copyfrom_path:
731 730 branched = True
732 731 newpath = ent.copyfrom_path + self.module[len(path):]
733 732 # ent.copyfrom_rev may not be the actual last revision
734 733 previd = self.latest(newpath, ent.copyfrom_rev)
735 734 if previd is not None:
736 735 prevmodule, prevnum = self.revsplit(previd)[1:]
737 736 if prevnum >= self.startrev:
738 737 parents = [previd]
739 738 self.ui.note(_('found parent of branch %s at %d: %s\n') %
740 739 (self.module, prevnum, prevmodule))
741 740 else:
742 741 self.ui.debug("no copyfrom path, don't know what to do.\n")
743 742
744 743 paths = []
745 744 # filter out unrelated paths
746 745 for path, ent in orig_paths:
747 746 if self.getrelpath(path) is None:
748 747 continue
749 748 paths.append((path, ent))
750 749
751 750 # Example SVN datetime. Includes microseconds.
752 751 # ISO-8601 conformant
753 752 # '2007-01-04T17:35:00.902377Z'
754 753 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
755 754
756 755 log = message and self.recode(message) or ''
757 756 author = author and self.recode(author) or ''
758 757 try:
759 758 branch = self.module.split("/")[-1]
760 759 if branch == 'trunk':
761 760 branch = ''
762 761 except IndexError:
763 762 branch = None
764 763
765 764 cset = commit(author=author,
766 765 date=util.datestr(date),
767 766 desc=log,
768 767 parents=parents,
769 768 branch=branch,
770 769 rev=rev)
771 770
772 771 self.commits[rev] = cset
773 772 # The parents list is *shared* among self.paths and the
774 773 # commit object. Both will be updated below.
775 774 self.paths[rev] = (paths, cset.parents)
776 775 if self.child_cset and not self.child_cset.parents:
777 776 self.child_cset.parents[:] = [rev]
778 777 self.child_cset = cset
779 778 return cset, branched
780 779
781 780 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
782 781 (self.module, from_revnum, to_revnum))
783 782
784 783 try:
785 784 firstcset = None
786 785 lastonbranch = False
787 786 stream = self._getlog([self.module], from_revnum, to_revnum)
788 787 try:
789 788 for entry in stream:
790 789 paths, revnum, author, date, message = entry
791 790 if revnum < self.startrev:
792 791 lastonbranch = True
793 792 break
794 793 if not paths:
795 794 self.ui.debug('revision %d has no entries\n' % revnum)
796 795 continue
797 796 cset, lastonbranch = parselogentry(paths, revnum, author,
798 797 date, message)
799 798 if cset:
800 799 firstcset = cset
801 800 if lastonbranch:
802 801 break
803 802 finally:
804 803 stream.close()
805 804
806 805 if not lastonbranch and firstcset and not firstcset.parents:
807 806 # The first revision of the sequence (the last fetched one)
808 807 # has invalid parents if not a branch root. Find the parent
809 808 # revision now, if any.
810 809 try:
811 810 firstrevnum = self.revnum(firstcset.rev)
812 811 if firstrevnum > 1:
813 812 latest = self.latest(self.module, firstrevnum - 1)
814 813 if latest:
815 814 firstcset.parents.append(latest)
816 815 except SvnPathNotFound:
817 816 pass
818 817 except SubversionException, (inst, num):
819 818 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
820 819 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
821 820 raise
822 821
823 822 def _getfile(self, file, rev):
824 823 # TODO: ra.get_file transmits the whole file instead of diffs.
825 824 mode = ''
826 825 try:
827 826 new_module, revnum = self.revsplit(rev)[1:]
828 827 if self.module != new_module:
829 828 self.module = new_module
830 829 self.reparent(self.module)
831 830 io = StringIO()
832 831 info = svn.ra.get_file(self.ra, file, revnum, io)
833 832 data = io.getvalue()
834 833 # ra.get_files() seems to keep a reference on the input buffer
835 834 # preventing collection. Release it explicitely.
836 835 io.close()
837 836 if isinstance(info, list):
838 837 info = info[-1]
839 838 mode = ("svn:executable" in info) and 'x' or ''
840 839 mode = ("svn:special" in info) and 'l' or mode
841 840 except SubversionException, e:
842 841 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
843 842 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
844 843 if e.apr_err in notfound: # File not found
845 844 raise IOError()
846 845 raise
847 846 if mode == 'l':
848 847 link_prefix = "link "
849 848 if data.startswith(link_prefix):
850 849 data = data[len(link_prefix):]
851 850 return data, mode
852 851
853 852 def _find_children(self, path, revnum):
854 853 path = path.strip('/')
855 854 pool = Pool()
856 855 rpath = '/'.join([self.baseurl, urllib.quote(path)]).strip('/')
857 856 return ['%s/%s' % (path, x) for x in
858 857 svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
859 858
860 859 def getrelpath(self, path, module=None):
861 860 if module is None:
862 861 module = self.module
863 862 # Given the repository url of this wc, say
864 863 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
865 864 # extract the "entry" portion (a relative path) from what
866 865 # svn log --xml says, ie
867 866 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
868 867 # that is to say "tests/PloneTestCase.py"
869 868 if path.startswith(module):
870 869 relative = path.rstrip('/')[len(module):]
871 870 if relative.startswith('/'):
872 871 return relative[1:]
873 872 elif relative == '':
874 873 return relative
875 874
876 875 # The path is outside our tracked tree...
877 876 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
878 877 return None
879 878
880 879 def _checkpath(self, path, revnum):
881 880 # ra.check_path does not like leading slashes very much, it leads
882 881 # to PROPFIND subversion errors
883 882 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
884 883
885 884 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
886 885 strict_node_history=False):
887 886 # Normalize path names, svn >= 1.5 only wants paths relative to
888 887 # supplied URL
889 888 relpaths = []
890 889 for p in paths:
891 890 if not p.startswith('/'):
892 891 p = self.module + '/' + p
893 892 relpaths.append(p.strip('/'))
894 893 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
895 894 strict_node_history]
896 895 arg = encodeargs(args)
897 896 hgexe = util.hgexecutable()
898 897 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
899 898 stdin, stdout = util.popen2(cmd)
900 899 stdin.write(arg)
901 900 stdin.close()
902 901 return logstream(stdout)
903 902
904 903 pre_revprop_change = '''#!/bin/sh
905 904
906 905 REPOS="$1"
907 906 REV="$2"
908 907 USER="$3"
909 908 PROPNAME="$4"
910 909 ACTION="$5"
911 910
912 911 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
913 912 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
914 913 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
915 914
916 915 echo "Changing prohibited revision property" >&2
917 916 exit 1
918 917 '''
919 918
920 919 class svn_sink(converter_sink, commandline):
921 920 commit_re = re.compile(r'Committed revision (\d+).', re.M)
922 921
923 922 def prerun(self):
924 923 if self.wc:
925 924 os.chdir(self.wc)
926 925
927 926 def postrun(self):
928 927 if self.wc:
929 928 os.chdir(self.cwd)
930 929
931 930 def join(self, name):
932 931 return os.path.join(self.wc, '.svn', name)
933 932
934 933 def revmapfile(self):
935 934 return self.join('hg-shamap')
936 935
937 936 def authorfile(self):
938 937 return self.join('hg-authormap')
939 938
940 939 def __init__(self, ui, path):
941 940 converter_sink.__init__(self, ui, path)
942 941 commandline.__init__(self, ui, 'svn')
943 942 self.delete = []
944 943 self.setexec = []
945 944 self.delexec = []
946 945 self.copies = []
947 946 self.wc = None
948 947 self.cwd = os.getcwd()
949 948
950 949 path = os.path.realpath(path)
951 950
952 951 created = False
953 952 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
954 953 self.wc = path
955 954 self.run0('update')
956 955 else:
957 956 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
958 957
959 958 if os.path.isdir(os.path.dirname(path)):
960 959 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
961 960 ui.status(_('initializing svn repo %r\n') %
962 961 os.path.basename(path))
963 962 commandline(ui, 'svnadmin').run0('create', path)
964 963 created = path
965 964 path = util.normpath(path)
966 965 if not path.startswith('/'):
967 966 path = '/' + path
968 967 path = 'file://' + path
969 968
970 969 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
971 970 self.run0('checkout', path, wcpath)
972 971
973 972 self.wc = wcpath
974 973 self.opener = util.opener(self.wc)
975 974 self.wopener = util.opener(self.wc)
976 975 self.childmap = mapfile(ui, self.join('hg-childmap'))
977 976 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
978 977
979 978 if created:
980 979 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
981 980 fp = open(hook, 'w')
982 981 fp.write(pre_revprop_change)
983 982 fp.close()
984 983 util.set_flags(hook, False, True)
985 984
986 985 xport = transport.SvnRaTransport(url=geturl(path))
987 986 self.uuid = svn.ra.get_uuid(xport.ra)
988 987
989 988 def wjoin(self, *names):
990 989 return os.path.join(self.wc, *names)
991 990
992 991 def putfile(self, filename, flags, data):
993 992 if 'l' in flags:
994 993 self.wopener.symlink(data, filename)
995 994 else:
996 995 try:
997 996 if os.path.islink(self.wjoin(filename)):
998 997 os.unlink(filename)
999 998 except OSError:
1000 999 pass
1001 1000 self.wopener(filename, 'w').write(data)
1002 1001
1003 1002 if self.is_exec:
1004 1003 was_exec = self.is_exec(self.wjoin(filename))
1005 1004 else:
1006 1005 # On filesystems not supporting execute-bit, there is no way
1007 1006 # to know if it is set but asking subversion. Setting it
1008 1007 # systematically is just as expensive and much simpler.
1009 1008 was_exec = 'x' not in flags
1010 1009
1011 1010 util.set_flags(self.wjoin(filename), False, 'x' in flags)
1012 1011 if was_exec:
1013 1012 if 'x' not in flags:
1014 1013 self.delexec.append(filename)
1015 1014 else:
1016 1015 if 'x' in flags:
1017 1016 self.setexec.append(filename)
1018 1017
1019 1018 def _copyfile(self, source, dest):
1020 1019 # SVN's copy command pukes if the destination file exists, but
1021 1020 # our copyfile method expects to record a copy that has
1022 1021 # already occurred. Cross the semantic gap.
1023 1022 wdest = self.wjoin(dest)
1024 1023 exists = os.path.exists(wdest)
1025 1024 if exists:
1026 1025 fd, tempname = tempfile.mkstemp(
1027 1026 prefix='hg-copy-', dir=os.path.dirname(wdest))
1028 1027 os.close(fd)
1029 1028 os.unlink(tempname)
1030 1029 os.rename(wdest, tempname)
1031 1030 try:
1032 1031 self.run0('copy', source, dest)
1033 1032 finally:
1034 1033 if exists:
1035 1034 try:
1036 1035 os.unlink(wdest)
1037 1036 except OSError:
1038 1037 pass
1039 1038 os.rename(tempname, wdest)
1040 1039
1041 1040 def dirs_of(self, files):
1042 1041 dirs = set()
1043 1042 for f in files:
1044 1043 if os.path.isdir(self.wjoin(f)):
1045 1044 dirs.add(f)
1046 1045 for i in strutil.rfindall(f, '/'):
1047 1046 dirs.add(f[:i])
1048 1047 return dirs
1049 1048
1050 1049 def add_dirs(self, files):
1051 1050 add_dirs = [d for d in sorted(self.dirs_of(files))
1052 1051 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1053 1052 if add_dirs:
1054 1053 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1055 1054 return add_dirs
1056 1055
1057 1056 def add_files(self, files):
1058 1057 if files:
1059 1058 self.xargs(files, 'add', quiet=True)
1060 1059 return files
1061 1060
1062 1061 def tidy_dirs(self, names):
1063 1062 deleted = []
1064 1063 for d in sorted(self.dirs_of(names), reverse=True):
1065 1064 wd = self.wjoin(d)
1066 1065 if os.listdir(wd) == '.svn':
1067 1066 self.run0('delete', d)
1068 1067 deleted.append(d)
1069 1068 return deleted
1070 1069
1071 1070 def addchild(self, parent, child):
1072 1071 self.childmap[parent] = child
1073 1072
1074 1073 def revid(self, rev):
1075 1074 return u"svn:%s@%s" % (self.uuid, rev)
1076 1075
1077 1076 def putcommit(self, files, copies, parents, commit, source, revmap):
1078 1077 # Apply changes to working copy
1079 1078 for f, v in files:
1080 1079 try:
1081 1080 data = source.getfile(f, v)
1082 1081 except IOError:
1083 1082 self.delete.append(f)
1084 1083 else:
1085 1084 e = source.getmode(f, v)
1086 1085 self.putfile(f, e, data)
1087 1086 if f in copies:
1088 1087 self.copies.append([copies[f], f])
1089 1088 files = [f[0] for f in files]
1090 1089
1091 1090 for parent in parents:
1092 1091 try:
1093 1092 return self.revid(self.childmap[parent])
1094 1093 except KeyError:
1095 1094 pass
1096 1095 entries = set(self.delete)
1097 1096 files = frozenset(files)
1098 1097 entries.update(self.add_dirs(files.difference(entries)))
1099 1098 if self.copies:
1100 1099 for s, d in self.copies:
1101 1100 self._copyfile(s, d)
1102 1101 self.copies = []
1103 1102 if self.delete:
1104 1103 self.xargs(self.delete, 'delete')
1105 1104 self.delete = []
1106 1105 entries.update(self.add_files(files.difference(entries)))
1107 1106 entries.update(self.tidy_dirs(entries))
1108 1107 if self.delexec:
1109 1108 self.xargs(self.delexec, 'propdel', 'svn:executable')
1110 1109 self.delexec = []
1111 1110 if self.setexec:
1112 1111 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1113 1112 self.setexec = []
1114 1113
1115 1114 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1116 1115 fp = os.fdopen(fd, 'w')
1117 1116 fp.write(commit.desc)
1118 1117 fp.close()
1119 1118 try:
1120 1119 output = self.run0('commit',
1121 1120 username=util.shortuser(commit.author),
1122 1121 file=messagefile,
1123 1122 encoding='utf-8')
1124 1123 try:
1125 1124 rev = self.commit_re.search(output).group(1)
1126 1125 except AttributeError:
1127 1126 self.ui.warn(_('unexpected svn output:\n'))
1128 1127 self.ui.warn(output)
1129 1128 raise util.Abort(_('unable to cope with svn output'))
1130 1129 if commit.rev:
1131 1130 self.run('propset', 'hg:convert-rev', commit.rev,
1132 1131 revprop=True, revision=rev)
1133 1132 if commit.branch and commit.branch != 'default':
1134 1133 self.run('propset', 'hg:convert-branch', commit.branch,
1135 1134 revprop=True, revision=rev)
1136 1135 for parent in parents:
1137 1136 self.addchild(parent, rev)
1138 1137 return self.revid(rev)
1139 1138 finally:
1140 1139 os.unlink(messagefile)
1141 1140
1142 1141 def puttags(self, tags):
1143 1142 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
@@ -1,278 +1,276 b''
1 1 #!/usr/bin/env python
2 2 """Test the running system for features availability. Exit with zero
3 3 if all features are there, non-zero otherwise. If a feature name is
4 4 prefixed with "no-", the absence of feature is tested.
5 5 """
6 6 import optparse
7 7 import os
8 8 import re
9 9 import sys
10 10 import tempfile
11 11
12 12 tempprefix = 'hg-hghave-'
13 13
14 14 def matchoutput(cmd, regexp, ignorestatus=False):
15 15 """Return True if cmd executes successfully and its output
16 16 is matched by the supplied regular expression.
17 17 """
18 18 r = re.compile(regexp)
19 19 fh = os.popen(cmd)
20 20 s = fh.read()
21 21 try:
22 22 ret = fh.close()
23 23 except IOError:
24 24 # Happen in Windows test environment
25 25 ret = 1
26 26 return (ignorestatus or ret is None) and r.search(s)
27 27
28 28 def has_baz():
29 29 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
30 30
31 31 def has_bzr():
32 32 try:
33 33 import bzrlib
34 34 return bzrlib.__doc__ != None
35 35 except ImportError:
36 36 return False
37 37
38 38 def has_bzr114():
39 39 try:
40 40 import bzrlib
41 41 return (bzrlib.__doc__ != None
42 42 and bzrlib.version_info[:2] >= (1, 14))
43 43 except ImportError:
44 44 return False
45 45
46 46 def has_cvs():
47 47 re = r'Concurrent Versions System.*?server'
48 48 return matchoutput('cvs --version 2>&1', re)
49 49
50 50 def has_darcs():
51 51 return matchoutput('darcs --version', r'2\.[2-9]', True)
52 52
53 53 def has_mtn():
54 54 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
55 55 'mtn --version', r'monotone 0\.(\d|[12]\d|3[01])[^\d]', True)
56 56
57 57 def has_eol_in_paths():
58 58 try:
59 59 fd, path = tempfile.mkstemp(prefix=tempprefix, suffix='\n\r')
60 60 os.close(fd)
61 61 os.remove(path)
62 62 return True
63 63 except:
64 64 return False
65 65
66 66 def has_executablebit():
67 67 fd, path = tempfile.mkstemp(prefix=tempprefix)
68 68 os.close(fd)
69 69 try:
70 70 s = os.lstat(path).st_mode
71 71 os.chmod(path, s | 0100)
72 72 return (os.lstat(path).st_mode & 0100 != 0)
73 73 finally:
74 74 os.remove(path)
75 75
76 76 def has_icasefs():
77 77 # Stolen from mercurial.util
78 78 fd, path = tempfile.mkstemp(prefix=tempprefix, dir='.')
79 79 os.close(fd)
80 80 try:
81 81 s1 = os.stat(path)
82 82 d, b = os.path.split(path)
83 83 p2 = os.path.join(d, b.upper())
84 84 if path == p2:
85 85 p2 = os.path.join(d, b.lower())
86 86 try:
87 87 s2 = os.stat(p2)
88 88 return s2 == s1
89 89 except:
90 90 return False
91 91 finally:
92 92 os.remove(path)
93 93
94 94 def has_inotify():
95 95 try:
96 96 import hgext.inotify.linux.watcher
97 97 return True
98 98 except ImportError:
99 99 return False
100 100
101 101 def has_fifo():
102 102 return hasattr(os, "mkfifo")
103 103
104 104 def has_hotshot():
105 105 try:
106 106 # hotshot.stats tests hotshot and many problematic dependencies
107 107 # like profile.
108 108 import hotshot.stats
109 109 return True
110 110 except ImportError:
111 111 return False
112 112
113 113 def has_lsprof():
114 114 try:
115 115 import _lsprof
116 116 return True
117 117 except ImportError:
118 118 return False
119 119
120 120 def has_git():
121 121 return matchoutput('git --version 2>&1', r'^git version')
122 122
123 123 def has_rst2html():
124 124 for name in ('rst2html', 'rst2html.py'):
125 125 name = name + ' --version 2>&1'
126 126 if matchoutput(name, r'^rst2html(?:\.py)? \(Docutils'):
127 127 return True
128 128 return False
129 129
130 130 def has_svn():
131 #return matchoutput('svn --version 2>&1', r'^svn, version') and \
132 #matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
133 # disabled until licensing issue is resolved
134 return False
131 return matchoutput('svn --version 2>&1', r'^svn, version') and \
132 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
135 133
136 134 def has_svn_bindings():
137 135 try:
138 136 import svn.core
139 137 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
140 138 if version < (1, 4):
141 139 return False
142 140 return True
143 141 except ImportError:
144 142 return False
145 143
146 144 def has_p4():
147 145 return matchoutput('p4 -V', r'Rev\. P4/') and matchoutput('p4d -V', r'Rev\. P4D/')
148 146
149 147 def has_symlink():
150 148 return hasattr(os, "symlink")
151 149
152 150 def has_tla():
153 151 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
154 152
155 153 def has_gpg():
156 154 return matchoutput('gpg --version 2>&1', r'GnuPG')
157 155
158 156 def has_unix_permissions():
159 157 d = tempfile.mkdtemp(prefix=tempprefix, dir=".")
160 158 try:
161 159 fname = os.path.join(d, 'foo')
162 160 for umask in (077, 007, 022):
163 161 os.umask(umask)
164 162 f = open(fname, 'w')
165 163 f.close()
166 164 mode = os.stat(fname).st_mode
167 165 os.unlink(fname)
168 166 if mode & 0777 != ~umask & 0666:
169 167 return False
170 168 return True
171 169 finally:
172 170 os.rmdir(d)
173 171
174 172 def has_pygments():
175 173 try:
176 174 import pygments
177 175 return True
178 176 except ImportError:
179 177 return False
180 178
181 179 def has_outer_repo():
182 180 return matchoutput('hg root 2>&1', r'')
183 181
184 182 checks = {
185 183 "baz": (has_baz, "GNU Arch baz client"),
186 184 "bzr": (has_bzr, "Canonical's Bazaar client"),
187 185 "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"),
188 186 "cvs": (has_cvs, "cvs client/server"),
189 187 "darcs": (has_darcs, "darcs client"),
190 188 "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"),
191 189 "execbit": (has_executablebit, "executable bit"),
192 190 "fifo": (has_fifo, "named pipes"),
193 191 "git": (has_git, "git command line client"),
194 192 "gpg": (has_gpg, "gpg client"),
195 193 "hotshot": (has_hotshot, "python hotshot module"),
196 194 "icasefs": (has_icasefs, "case insensitive file system"),
197 195 "inotify": (has_inotify, "inotify extension support"),
198 196 "lsprof": (has_lsprof, "python lsprof module"),
199 197 "mtn": (has_mtn, "monotone client (> 0.31)"),
200 198 "outer-repo": (has_outer_repo, "outer repo"),
201 199 "p4": (has_p4, "Perforce server and client"),
202 200 "pygments": (has_pygments, "Pygments source highlighting library"),
203 201 "rst2html": (has_rst2html, "Docutils rst2html tool"),
204 202 "svn": (has_svn, "subversion client and admin tools"),
205 203 "svn-bindings": (has_svn_bindings, "subversion python bindings"),
206 204 "symlink": (has_symlink, "symbolic links"),
207 205 "tla": (has_tla, "GNU Arch tla client"),
208 206 "unix-permissions": (has_unix_permissions, "unix-style permissions"),
209 207 }
210 208
211 209 def list_features():
212 210 for name, feature in checks.iteritems():
213 211 desc = feature[1]
214 212 print name + ':', desc
215 213
216 214 def test_features():
217 215 failed = 0
218 216 for name, feature in checks.iteritems():
219 217 check, _ = feature
220 218 try:
221 219 check()
222 220 except Exception, e:
223 221 print "feature %s failed: %s" % (name, e)
224 222 failed += 1
225 223 return failed
226 224
227 225 parser = optparse.OptionParser("%prog [options] [features]")
228 226 parser.add_option("--test-features", action="store_true",
229 227 help="test available features")
230 228 parser.add_option("--list-features", action="store_true",
231 229 help="list available features")
232 230 parser.add_option("-q", "--quiet", action="store_true",
233 231 help="check features silently")
234 232
235 233 if __name__ == '__main__':
236 234 options, args = parser.parse_args()
237 235 if options.list_features:
238 236 list_features()
239 237 sys.exit(0)
240 238
241 239 if options.test_features:
242 240 sys.exit(test_features())
243 241
244 242 quiet = options.quiet
245 243
246 244 failures = 0
247 245
248 246 def error(msg):
249 247 global failures
250 248 if not quiet:
251 249 sys.stderr.write(msg + '\n')
252 250 failures += 1
253 251
254 252 for feature in args:
255 253 negate = feature.startswith('no-')
256 254 if negate:
257 255 feature = feature[3:]
258 256
259 257 if feature not in checks:
260 258 error('skipped: unknown feature: ' + feature)
261 259 continue
262 260
263 261 check, desc = checks[feature]
264 262 try:
265 263 available = check()
266 264 except Exception, e:
267 265 error('hghave check failed: ' + feature)
268 266 continue
269 267
270 268 if not negate and not available:
271 269 error('skipped: missing feature: ' + desc)
272 270 elif negate and available:
273 271 error('skipped: system supports %s' % desc)
274 272
275 273 if failures != 0:
276 274 sys.exit(1)
277 275
278 276
General Comments 0
You need to be logged in to leave comments. Login now