##// END OF EJS Templates
pep8: fixed issue with shadowing reserved python variables.
marcink -
r119:36ec6f9e default
parent child Browse files
Show More
@@ -1,724 +1,724 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2016 RodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import io
19 19 import logging
20 20 import stat
21 21 import sys
22 22 import urllib
23 23 import urllib2
24 24
25 25 from hgext import largefiles, rebase
26 26 from hgext.strip import strip as hgext_strip
27 27 from mercurial import commands
28 28 from mercurial import unionrepo
29 29
30 30 from vcsserver import exceptions
31 31 from vcsserver.base import RepoFactory, obfuscate_qs
32 32 from vcsserver.hgcompat import (
33 33 archival, bin, clone, config as hgconfig, diffopts, hex,
34 34 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
35 35 httppeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
36 36 patch, peer, revrange, ui, Abort, LookupError, RepoError, RepoLookupError,
37 37 InterventionRequired, RequirementError)
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 def make_ui_from_config(repo_config):
43 43 baseui = ui.ui()
44 44
45 45 # clean the baseui object
46 46 baseui._ocfg = hgconfig.config()
47 47 baseui._ucfg = hgconfig.config()
48 48 baseui._tcfg = hgconfig.config()
49 49
50 50 for section, option, value in repo_config:
51 51 baseui.setconfig(section, option, value)
52 52
53 53 # make our hgweb quiet so it doesn't print output
54 54 baseui.setconfig('ui', 'quiet', 'true')
55 55
56 56 # force mercurial to only use 1 thread, otherwise it may try to set a
57 57 # signal in a non-main thread, thus generating a ValueError.
58 58 baseui.setconfig('worker', 'numcpus', 1)
59 59
60 60 # If there is no config for the largefiles extension, we explicitly disable
61 61 # it here. This overrides settings from repositories hgrc file. Recent
62 62 # mercurial versions enable largefiles in hgrc on clone from largefile
63 63 # repo.
64 64 if not baseui.hasconfig('extensions', 'largefiles'):
65 65 log.debug('Explicitly disable largefiles extension for repo.')
66 66 baseui.setconfig('extensions', 'largefiles', '!')
67 67
68 68 return baseui
69 69
70 70
71 71 def reraise_safe_exceptions(func):
72 72 """Decorator for converting mercurial exceptions to something neutral."""
73 73 def wrapper(*args, **kwargs):
74 74 try:
75 75 return func(*args, **kwargs)
76 76 except (Abort, InterventionRequired):
77 77 raise_from_original(exceptions.AbortException)
78 78 except RepoLookupError:
79 79 raise_from_original(exceptions.LookupException)
80 80 except RequirementError:
81 81 raise_from_original(exceptions.RequirementException)
82 82 except RepoError:
83 83 raise_from_original(exceptions.VcsException)
84 84 except LookupError:
85 85 raise_from_original(exceptions.LookupException)
86 86 except Exception as e:
87 87 if not hasattr(e, '_vcs_kind'):
88 88 log.exception("Unhandled exception in hg remote call")
89 89 raise_from_original(exceptions.UnhandledException)
90 90 raise
91 91 return wrapper
92 92
93 93
94 94 def raise_from_original(new_type):
95 95 """
96 96 Raise a new exception type with original args and traceback.
97 97 """
98 98 _, original, traceback = sys.exc_info()
99 99 try:
100 100 raise new_type(*original.args), None, traceback
101 101 finally:
102 102 del traceback
103 103
104 104
105 105 class MercurialFactory(RepoFactory):
106 106
107 107 def _create_config(self, config, hooks=True):
108 108 if not hooks:
109 109 hooks_to_clean = frozenset((
110 110 'changegroup.repo_size', 'preoutgoing.pre_pull',
111 111 'outgoing.pull_logger', 'prechangegroup.pre_push'))
112 112 new_config = []
113 113 for section, option, value in config:
114 114 if section == 'hooks' and option in hooks_to_clean:
115 115 continue
116 116 new_config.append((section, option, value))
117 117 config = new_config
118 118
119 119 baseui = make_ui_from_config(config)
120 120 return baseui
121 121
122 122 def _create_repo(self, wire, create):
123 123 baseui = self._create_config(wire["config"])
124 124 return localrepository(baseui, wire["path"], create)
125 125
126 126
127 127 class HgRemote(object):
128 128
129 129 def __init__(self, factory):
130 130 self._factory = factory
131 131
132 132 self._bulk_methods = {
133 133 "affected_files": self.ctx_files,
134 134 "author": self.ctx_user,
135 135 "branch": self.ctx_branch,
136 136 "children": self.ctx_children,
137 137 "date": self.ctx_date,
138 138 "message": self.ctx_description,
139 139 "parents": self.ctx_parents,
140 140 "status": self.ctx_status,
141 141 "_file_paths": self.ctx_list,
142 142 }
143 143
144 144 @reraise_safe_exceptions
145 145 def discover_hg_version(self):
146 146 from mercurial import util
147 147 return util.version()
148 148
149 149 @reraise_safe_exceptions
150 150 def archive_repo(self, archive_path, mtime, file_info, kind):
151 151 if kind == "tgz":
152 152 archiver = archival.tarit(archive_path, mtime, "gz")
153 153 elif kind == "tbz2":
154 154 archiver = archival.tarit(archive_path, mtime, "bz2")
155 155 elif kind == 'zip':
156 156 archiver = archival.zipit(archive_path, mtime)
157 157 else:
158 158 raise exceptions.ArchiveException(
159 159 'Remote does not support: "%s".' % kind)
160 160
161 161 for f_path, f_mode, f_is_link, f_content in file_info:
162 162 archiver.addfile(f_path, f_mode, f_is_link, f_content)
163 163 archiver.done()
164 164
165 165 @reraise_safe_exceptions
166 166 def bookmarks(self, wire):
167 167 repo = self._factory.repo(wire)
168 168 return dict(repo._bookmarks)
169 169
170 170 @reraise_safe_exceptions
171 171 def branches(self, wire, normal, closed):
172 172 repo = self._factory.repo(wire)
173 173 iter_branches = repo.branchmap().iterbranches()
174 174 bt = {}
175 175 for branch_name, _heads, tip, is_closed in iter_branches:
176 176 if normal and not is_closed:
177 177 bt[branch_name] = tip
178 178 if closed and is_closed:
179 179 bt[branch_name] = tip
180 180
181 181 return bt
182 182
183 183 @reraise_safe_exceptions
184 184 def bulk_request(self, wire, rev, pre_load):
185 185 result = {}
186 186 for attr in pre_load:
187 187 try:
188 188 method = self._bulk_methods[attr]
189 189 result[attr] = method(wire, rev)
190 190 except KeyError:
191 191 raise exceptions.VcsException(
192 192 'Unknown bulk attribute: "%s"' % attr)
193 193 return result
194 194
195 195 @reraise_safe_exceptions
196 196 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
197 197 baseui = self._factory._create_config(wire["config"], hooks=hooks)
198 198 clone(baseui, source, dest, noupdate=not update_after_clone)
199 199
200 200 @reraise_safe_exceptions
201 201 def commitctx(
202 202 self, wire, message, parents, commit_time, commit_timezone,
203 203 user, files, extra, removed, updated):
204 204
205 205 def _filectxfn(_repo, memctx, path):
206 206 """
207 207 Marks given path as added/changed/removed in a given _repo. This is
208 208 for internal mercurial commit function.
209 209 """
210 210
211 211 # check if this path is removed
212 212 if path in removed:
213 213 # returning None is a way to mark node for removal
214 214 return None
215 215
216 216 # check if this path is added
217 217 for node in updated:
218 218 if node['path'] == path:
219 219 return memfilectx(
220 220 _repo,
221 221 path=node['path'],
222 222 data=node['content'],
223 223 islink=False,
224 224 isexec=bool(node['mode'] & stat.S_IXUSR),
225 225 copied=False,
226 226 memctx=memctx)
227 227
228 228 raise exceptions.AbortException(
229 229 "Given path haven't been marked as added, "
230 230 "changed or removed (%s)" % path)
231 231
232 232 repo = self._factory.repo(wire)
233 233
234 234 commit_ctx = memctx(
235 235 repo=repo,
236 236 parents=parents,
237 237 text=message,
238 238 files=files,
239 239 filectxfn=_filectxfn,
240 240 user=user,
241 241 date=(commit_time, commit_timezone),
242 242 extra=extra)
243 243
244 244 n = repo.commitctx(commit_ctx)
245 245 new_id = hex(n)
246 246
247 247 return new_id
248 248
249 249 @reraise_safe_exceptions
250 250 def ctx_branch(self, wire, revision):
251 251 repo = self._factory.repo(wire)
252 252 ctx = repo[revision]
253 253 return ctx.branch()
254 254
255 255 @reraise_safe_exceptions
256 256 def ctx_children(self, wire, revision):
257 257 repo = self._factory.repo(wire)
258 258 ctx = repo[revision]
259 259 return [child.rev() for child in ctx.children()]
260 260
261 261 @reraise_safe_exceptions
262 262 def ctx_date(self, wire, revision):
263 263 repo = self._factory.repo(wire)
264 264 ctx = repo[revision]
265 265 return ctx.date()
266 266
267 267 @reraise_safe_exceptions
268 268 def ctx_description(self, wire, revision):
269 269 repo = self._factory.repo(wire)
270 270 ctx = repo[revision]
271 271 return ctx.description()
272 272
273 273 @reraise_safe_exceptions
274 274 def ctx_diff(
275 275 self, wire, revision, git=True, ignore_whitespace=True, context=3):
276 276 repo = self._factory.repo(wire)
277 277 ctx = repo[revision]
278 278 result = ctx.diff(
279 279 git=git, ignore_whitespace=ignore_whitespace, context=context)
280 280 return list(result)
281 281
282 282 @reraise_safe_exceptions
283 283 def ctx_files(self, wire, revision):
284 284 repo = self._factory.repo(wire)
285 285 ctx = repo[revision]
286 286 return ctx.files()
287 287
288 288 @reraise_safe_exceptions
289 289 def ctx_list(self, path, revision):
290 290 repo = self._factory.repo(path)
291 291 ctx = repo[revision]
292 292 return list(ctx)
293 293
294 294 @reraise_safe_exceptions
295 295 def ctx_parents(self, wire, revision):
296 296 repo = self._factory.repo(wire)
297 297 ctx = repo[revision]
298 298 return [parent.rev() for parent in ctx.parents()]
299 299
300 300 @reraise_safe_exceptions
301 301 def ctx_substate(self, wire, revision):
302 302 repo = self._factory.repo(wire)
303 303 ctx = repo[revision]
304 304 return ctx.substate
305 305
306 306 @reraise_safe_exceptions
307 307 def ctx_status(self, wire, revision):
308 308 repo = self._factory.repo(wire)
309 309 ctx = repo[revision]
310 310 status = repo[ctx.p1().node()].status(other=ctx.node())
311 311 # object of status (odd, custom named tuple in mercurial) is not
312 312 # correctly serializable via Pyro, we make it a list, as the underling
313 313 # API expects this to be a list
314 314 return list(status)
315 315
316 316 @reraise_safe_exceptions
317 317 def ctx_user(self, wire, revision):
318 318 repo = self._factory.repo(wire)
319 319 ctx = repo[revision]
320 320 return ctx.user()
321 321
322 322 @reraise_safe_exceptions
323 323 def check_url(self, url, config):
324 324 _proto = None
325 325 if '+' in url[:url.find('://')]:
326 326 _proto = url[0:url.find('+')]
327 327 url = url[url.find('+') + 1:]
328 328 handlers = []
329 329 url_obj = url_parser(url)
330 330 test_uri, authinfo = url_obj.authinfo()
331 331 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
332 332 url_obj.query = obfuscate_qs(url_obj.query)
333 333
334 334 cleaned_uri = str(url_obj)
335 335 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
336 336
337 337 if authinfo:
338 338 # create a password manager
339 339 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
340 340 passmgr.add_password(*authinfo)
341 341
342 342 handlers.extend((httpbasicauthhandler(passmgr),
343 343 httpdigestauthhandler(passmgr)))
344 344
345 345 o = urllib2.build_opener(*handlers)
346 346 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
347 347 ('Accept', 'application/mercurial-0.1')]
348 348
349 349 q = {"cmd": 'between'}
350 350 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
351 351 qs = '?%s' % urllib.urlencode(q)
352 352 cu = "%s%s" % (test_uri, qs)
353 353 req = urllib2.Request(cu, None, {})
354 354
355 355 try:
356 356 log.debug("Trying to open URL %s", cleaned_uri)
357 357 resp = o.open(req)
358 358 if resp.code != 200:
359 359 raise exceptions.URLError('Return Code is not 200')
360 360 except Exception as e:
361 361 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
362 362 # means it cannot be cloned
363 363 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
364 364
365 365 # now check if it's a proper hg repo, but don't do it for svn
366 366 try:
367 367 if _proto == 'svn':
368 368 pass
369 369 else:
370 370 # check for pure hg repos
371 371 log.debug(
372 372 "Verifying if URL is a Mercurial repository: %s",
373 373 cleaned_uri)
374 374 httppeer(make_ui_from_config(config), url).lookup('tip')
375 375 except Exception as e:
376 376 log.warning("URL is not a valid Mercurial repository: %s",
377 377 cleaned_uri)
378 378 raise exceptions.URLError(
379 379 "url [%s] does not look like an hg repo org_exc: %s"
380 380 % (cleaned_uri, e))
381 381
382 382 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
383 383 return True
384 384
385 385 @reraise_safe_exceptions
386 386 def diff(
387 387 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
388 388 context):
389 389 repo = self._factory.repo(wire)
390 390
391 391 if file_filter:
392 filter = match(file_filter[0], '', [file_filter[1]])
392 match_filter = match(file_filter[0], '', [file_filter[1]])
393 393 else:
394 filter = file_filter
394 match_filter = file_filter
395 395 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
396 396
397 397 try:
398 398 return "".join(patch.diff(
399 repo, node1=rev1, node2=rev2, match=filter, opts=opts))
399 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
400 400 except RepoLookupError:
401 401 raise exceptions.LookupException()
402 402
403 403 @reraise_safe_exceptions
404 404 def file_history(self, wire, revision, path, limit):
405 405 repo = self._factory.repo(wire)
406 406
407 407 ctx = repo[revision]
408 408 fctx = ctx.filectx(path)
409 409
410 410 def history_iter():
411 411 limit_rev = fctx.rev()
412 412 for obj in reversed(list(fctx.filelog())):
413 413 obj = fctx.filectx(obj)
414 414 if limit_rev >= obj.rev():
415 415 yield obj
416 416
417 417 history = []
418 418 for cnt, obj in enumerate(history_iter()):
419 419 if limit and cnt >= limit:
420 420 break
421 421 history.append(hex(obj.node()))
422 422
423 423 return [x for x in history]
424 424
425 425 @reraise_safe_exceptions
426 426 def file_history_untill(self, wire, revision, path, limit):
427 427 repo = self._factory.repo(wire)
428 428 ctx = repo[revision]
429 429 fctx = ctx.filectx(path)
430 430
431 431 file_log = list(fctx.filelog())
432 432 if limit:
433 433 # Limit to the last n items
434 434 file_log = file_log[-limit:]
435 435
436 436 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
437 437
438 438 @reraise_safe_exceptions
439 439 def fctx_annotate(self, wire, revision, path):
440 440 repo = self._factory.repo(wire)
441 441 ctx = repo[revision]
442 442 fctx = ctx.filectx(path)
443 443
444 444 result = []
445 445 for i, annotate_data in enumerate(fctx.annotate()):
446 446 ln_no = i + 1
447 447 node_info, content = annotate_data
448 448 sha = hex(node_info[0].node())
449 449 result.append((ln_no, sha, content))
450 450 return result
451 451
452 452 @reraise_safe_exceptions
453 453 def fctx_data(self, wire, revision, path):
454 454 repo = self._factory.repo(wire)
455 455 ctx = repo[revision]
456 456 fctx = ctx.filectx(path)
457 457 return fctx.data()
458 458
459 459 @reraise_safe_exceptions
460 460 def fctx_flags(self, wire, revision, path):
461 461 repo = self._factory.repo(wire)
462 462 ctx = repo[revision]
463 463 fctx = ctx.filectx(path)
464 464 return fctx.flags()
465 465
466 466 @reraise_safe_exceptions
467 467 def fctx_size(self, wire, revision, path):
468 468 repo = self._factory.repo(wire)
469 469 ctx = repo[revision]
470 470 fctx = ctx.filectx(path)
471 471 return fctx.size()
472 472
473 473 @reraise_safe_exceptions
474 474 def get_all_commit_ids(self, wire, name):
475 475 repo = self._factory.repo(wire)
476 476 revs = repo.filtered(name).changelog.index
477 477 return map(lambda x: hex(x[7]), revs)[:-1]
478 478
479 479 @reraise_safe_exceptions
480 480 def get_config_value(self, wire, section, name, untrusted=False):
481 481 repo = self._factory.repo(wire)
482 482 return repo.ui.config(section, name, untrusted=untrusted)
483 483
484 484 @reraise_safe_exceptions
485 485 def get_config_bool(self, wire, section, name, untrusted=False):
486 486 repo = self._factory.repo(wire)
487 487 return repo.ui.configbool(section, name, untrusted=untrusted)
488 488
489 489 @reraise_safe_exceptions
490 490 def get_config_list(self, wire, section, name, untrusted=False):
491 491 repo = self._factory.repo(wire)
492 492 return repo.ui.configlist(section, name, untrusted=untrusted)
493 493
494 494 @reraise_safe_exceptions
495 495 def is_large_file(self, wire, path):
496 496 return largefiles.lfutil.isstandin(path)
497 497
498 498 @reraise_safe_exceptions
499 499 def in_store(self, wire, sha):
500 500 repo = self._factory.repo(wire)
501 501 return largefiles.lfutil.instore(repo, sha)
502 502
503 503 @reraise_safe_exceptions
504 504 def in_user_cache(self, wire, sha):
505 505 repo = self._factory.repo(wire)
506 506 return largefiles.lfutil.inusercache(repo.ui, sha)
507 507
508 508 @reraise_safe_exceptions
509 509 def store_path(self, wire, sha):
510 510 repo = self._factory.repo(wire)
511 511 return largefiles.lfutil.storepath(repo, sha)
512 512
513 513 @reraise_safe_exceptions
514 514 def link(self, wire, sha, path):
515 515 repo = self._factory.repo(wire)
516 516 largefiles.lfutil.link(
517 517 largefiles.lfutil.usercachepath(repo.ui, sha), path)
518 518
519 519 @reraise_safe_exceptions
520 520 def localrepository(self, wire, create=False):
521 521 self._factory.repo(wire, create=create)
522 522
523 523 @reraise_safe_exceptions
524 524 def lookup(self, wire, revision, both):
525 525 # TODO Paris: Ugly hack to "deserialize" long for msgpack
526 526 if isinstance(revision, float):
527 527 revision = long(revision)
528 528 repo = self._factory.repo(wire)
529 529 try:
530 530 ctx = repo[revision]
531 531 except RepoLookupError:
532 532 raise exceptions.LookupException(revision)
533 533 except LookupError as e:
534 534 raise exceptions.LookupException(e.name)
535 535
536 536 if not both:
537 537 return ctx.hex()
538 538
539 539 ctx = repo[ctx.hex()]
540 540 return ctx.hex(), ctx.rev()
541 541
542 542 @reraise_safe_exceptions
543 543 def pull(self, wire, url, commit_ids=None):
544 544 repo = self._factory.repo(wire)
545 545 remote = peer(repo, {}, url)
546 546 if commit_ids:
547 547 commit_ids = [bin(commit_id) for commit_id in commit_ids]
548 548
549 549 return exchange.pull(
550 550 repo, remote, heads=commit_ids, force=None).cgresult
551 551
552 552 @reraise_safe_exceptions
553 553 def revision(self, wire, rev):
554 554 repo = self._factory.repo(wire)
555 555 ctx = repo[rev]
556 556 return ctx.rev()
557 557
558 558 @reraise_safe_exceptions
559 559 def rev_range(self, wire, filter):
560 560 repo = self._factory.repo(wire)
561 561 revisions = [rev for rev in revrange(repo, filter)]
562 562 return revisions
563 563
564 564 @reraise_safe_exceptions
565 565 def rev_range_hash(self, wire, node):
566 566 repo = self._factory.repo(wire)
567 567
568 568 def get_revs(repo, rev_opt):
569 569 if rev_opt:
570 570 revs = revrange(repo, rev_opt)
571 571 if len(revs) == 0:
572 572 return (nullrev, nullrev)
573 573 return max(revs), min(revs)
574 574 else:
575 575 return len(repo) - 1, 0
576 576
577 577 stop, start = get_revs(repo, [node + ':'])
578 578 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
579 579 return revs
580 580
581 581 @reraise_safe_exceptions
582 582 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
583 583 other_path = kwargs.pop('other_path', None)
584 584
585 585 # case when we want to compare two independent repositories
586 586 if other_path and other_path != wire["path"]:
587 587 baseui = self._factory._create_config(wire["config"])
588 588 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
589 589 else:
590 590 repo = self._factory.repo(wire)
591 591 return list(repo.revs(rev_spec, *args))
592 592
593 593 @reraise_safe_exceptions
594 594 def strip(self, wire, revision, update, backup):
595 595 repo = self._factory.repo(wire)
596 596 ctx = repo[revision]
597 597 hgext_strip(
598 598 repo.baseui, repo, ctx.node(), update=update, backup=backup)
599 599
600 600 @reraise_safe_exceptions
601 601 def tag(self, wire, name, revision, message, local, user,
602 602 tag_time, tag_timezone):
603 603 repo = self._factory.repo(wire)
604 604 ctx = repo[revision]
605 605 node = ctx.node()
606 606
607 607 date = (tag_time, tag_timezone)
608 608 try:
609 609 repo.tag(name, node, message, local, user, date)
610 610 except Abort:
611 611 log.exception("Tag operation aborted")
612 612 raise exceptions.AbortException()
613 613
614 614 @reraise_safe_exceptions
615 615 def tags(self, wire):
616 616 repo = self._factory.repo(wire)
617 617 return repo.tags()
618 618
619 619 @reraise_safe_exceptions
620 620 def update(self, wire, node=None, clean=False):
621 621 repo = self._factory.repo(wire)
622 622 baseui = self._factory._create_config(wire['config'])
623 623 commands.update(baseui, repo, node=node, clean=clean)
624 624
625 625 @reraise_safe_exceptions
626 626 def identify(self, wire):
627 627 repo = self._factory.repo(wire)
628 628 baseui = self._factory._create_config(wire['config'])
629 629 output = io.BytesIO()
630 630 baseui.write = output.write
631 631 # This is required to get a full node id
632 632 baseui.debugflag = True
633 633 commands.identify(baseui, repo, id=True)
634 634
635 635 return output.getvalue()
636 636
637 637 @reraise_safe_exceptions
638 638 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
639 639 hooks=True):
640 640 repo = self._factory.repo(wire)
641 641 baseui = self._factory._create_config(wire['config'], hooks=hooks)
642 642
643 643 # Mercurial internally has a lot of logic that checks ONLY if
644 644 # option is defined, we just pass those if they are defined then
645 645 opts = {}
646 646 if bookmark:
647 647 opts['bookmark'] = bookmark
648 648 if branch:
649 649 opts['branch'] = branch
650 650 if revision:
651 651 opts['rev'] = revision
652 652
653 653 commands.pull(baseui, repo, source, **opts)
654 654
655 655 @reraise_safe_exceptions
656 656 def heads(self, wire, branch=None):
657 657 repo = self._factory.repo(wire)
658 658 baseui = self._factory._create_config(wire['config'])
659 659 output = io.BytesIO()
660 660
661 661 def write(data, **unused_kwargs):
662 662 output.write(data)
663 663
664 664 baseui.write = write
665 665 if branch:
666 666 args = [branch]
667 667 else:
668 668 args = []
669 669 commands.heads(baseui, repo, template='{node} ', *args)
670 670
671 671 return output.getvalue()
672 672
673 673 @reraise_safe_exceptions
674 674 def ancestor(self, wire, revision1, revision2):
675 675 repo = self._factory.repo(wire)
676 676 baseui = self._factory._create_config(wire['config'])
677 677 output = io.BytesIO()
678 678 baseui.write = output.write
679 679 commands.debugancestor(baseui, repo, revision1, revision2)
680 680
681 681 return output.getvalue()
682 682
683 683 @reraise_safe_exceptions
684 684 def push(self, wire, revisions, dest_path, hooks=True,
685 685 push_branches=False):
686 686 repo = self._factory.repo(wire)
687 687 baseui = self._factory._create_config(wire['config'], hooks=hooks)
688 688 commands.push(baseui, repo, dest=dest_path, rev=revisions,
689 689 new_branch=push_branches)
690 690
691 691 @reraise_safe_exceptions
692 692 def merge(self, wire, revision):
693 693 repo = self._factory.repo(wire)
694 694 baseui = self._factory._create_config(wire['config'])
695 695 repo.ui.setconfig('ui', 'merge', 'internal:dump')
696 696
697 697 # In case of sub repositories are used mercurial prompts the user in
698 698 # case of merge conflicts or different sub repository sources. By
699 699 # setting the interactive flag to `False` mercurial doesn't prompt the
700 700 # used but instead uses a default value.
701 701 repo.ui.setconfig('ui', 'interactive', False)
702 702
703 703 commands.merge(baseui, repo, rev=revision)
704 704
705 705 @reraise_safe_exceptions
706 706 def commit(self, wire, message, username):
707 707 repo = self._factory.repo(wire)
708 708 baseui = self._factory._create_config(wire['config'])
709 709 repo.ui.setconfig('ui', 'username', username)
710 710 commands.commit(baseui, repo, message=message)
711 711
712 712 @reraise_safe_exceptions
713 713 def rebase(self, wire, source=None, dest=None, abort=False):
714 714 repo = self._factory.repo(wire)
715 715 baseui = self._factory._create_config(wire['config'])
716 716 repo.ui.setconfig('ui', 'merge', 'internal:dump')
717 717 rebase.rebase(
718 718 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
719 719
720 720 @reraise_safe_exceptions
721 721 def bookmark(self, wire, bookmark, revision=None):
722 722 repo = self._factory.repo(wire)
723 723 baseui = self._factory._create_config(wire['config'])
724 724 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
@@ -1,207 +1,209 b''
1 1 # -*- coding: utf-8 -*-
2 2 #
3 3 # Copyright (C) 2004-2009 Edgewall Software
4 4 # Copyright (C) 2004-2006 Christopher Lenz <cmlenz@gmx.de>
5 5 # All rights reserved.
6 6 #
7 7 # This software is licensed as described in the file COPYING, which
8 8 # you should have received as part of this distribution. The terms
9 9 # are also available at http://trac.edgewall.org/wiki/TracLicense.
10 10 #
11 11 # This software consists of voluntary contributions made by many
12 12 # individuals. For the exact contribution history, see the revision
13 13 # history and logs, available at http://trac.edgewall.org/log/.
14 14 #
15 15 # Author: Christopher Lenz <cmlenz@gmx.de>
16 16
17 17 import difflib
18 18
19 19
20 20 def get_filtered_hunks(fromlines, tolines, context=None,
21 21 ignore_blank_lines=False, ignore_case=False,
22 22 ignore_space_changes=False):
23 23 """Retrieve differences in the form of `difflib.SequenceMatcher`
24 24 opcodes, grouped according to the ``context`` and ``ignore_*``
25 25 parameters.
26 26
27 27 :param fromlines: list of lines corresponding to the old content
28 28 :param tolines: list of lines corresponding to the new content
29 29 :param ignore_blank_lines: differences about empty lines only are ignored
30 30 :param ignore_case: upper case / lower case only differences are ignored
31 31 :param ignore_space_changes: differences in amount of spaces are ignored
32 32 :param context: the number of "equal" lines kept for representing
33 33 the context of the change
34 34 :return: generator of grouped `difflib.SequenceMatcher` opcodes
35 35
36 36 If none of the ``ignore_*`` parameters is `True`, there's nothing
37 37 to filter out the results will come straight from the
38 38 SequenceMatcher.
39 39 """
40 40 hunks = get_hunks(fromlines, tolines, context)
41 41 if ignore_space_changes or ignore_case or ignore_blank_lines:
42 42 hunks = filter_ignorable_lines(hunks, fromlines, tolines, context,
43 43 ignore_blank_lines, ignore_case,
44 44 ignore_space_changes)
45 45 return hunks
46 46
47 47
48 48 def get_hunks(fromlines, tolines, context=None):
49 49 """Generator yielding grouped opcodes describing differences .
50 50
51 51 See `get_filtered_hunks` for the parameter descriptions.
52 52 """
53 53 matcher = difflib.SequenceMatcher(None, fromlines, tolines)
54 54 if context is None:
55 55 return (hunk for hunk in [matcher.get_opcodes()])
56 56 else:
57 57 return matcher.get_grouped_opcodes(context)
58 58
59 59
60 60 def filter_ignorable_lines(hunks, fromlines, tolines, context,
61 61 ignore_blank_lines, ignore_case,
62 62 ignore_space_changes):
63 63 """Detect line changes that should be ignored and emits them as
64 64 tagged as "equal", possibly joined with the preceding and/or
65 65 following "equal" block.
66 66
67 67 See `get_filtered_hunks` for the parameter descriptions.
68 68 """
69 69 def is_ignorable(tag, fromlines, tolines):
70 70 if tag == 'delete' and ignore_blank_lines:
71 71 if ''.join(fromlines) == '':
72 72 return True
73 73 elif tag == 'insert' and ignore_blank_lines:
74 74 if ''.join(tolines) == '':
75 75 return True
76 76 elif tag == 'replace' and (ignore_case or ignore_space_changes):
77 77 if len(fromlines) != len(tolines):
78 78 return False
79 def f(str):
79
80 def f(input_str):
80 81 if ignore_case:
81 str = str.lower()
82 input_str = input_str.lower()
82 83 if ignore_space_changes:
83 str = ' '.join(str.split())
84 return str
84 input_str = ' '.join(input_str.split())
85 return input_str
86
85 87 for i in range(len(fromlines)):
86 88 if f(fromlines[i]) != f(tolines[i]):
87 89 return False
88 90 return True
89 91
90 92 hunks = list(hunks)
91 93 opcodes = []
92 94 ignored_lines = False
93 95 prev = None
94 96 for hunk in hunks:
95 97 for tag, i1, i2, j1, j2 in hunk:
96 98 if tag == 'equal':
97 99 if prev:
98 100 prev = (tag, prev[1], i2, prev[3], j2)
99 101 else:
100 102 prev = (tag, i1, i2, j1, j2)
101 103 else:
102 104 if is_ignorable(tag, fromlines[i1:i2], tolines[j1:j2]):
103 105 ignored_lines = True
104 106 if prev:
105 107 prev = 'equal', prev[1], i2, prev[3], j2
106 108 else:
107 109 prev = 'equal', i1, i2, j1, j2
108 110 continue
109 111 if prev:
110 112 opcodes.append(prev)
111 113 opcodes.append((tag, i1, i2, j1, j2))
112 114 prev = None
113 115 if prev:
114 116 opcodes.append(prev)
115 117
116 118 if ignored_lines:
117 119 if context is None:
118 120 yield opcodes
119 121 else:
120 122 # we leave at most n lines with the tag 'equal' before and after
121 123 # every change
122 124 n = context
123 125 nn = n + n
124 126
125 127 group = []
126 128 def all_equal():
127 129 all(op[0] == 'equal' for op in group)
128 130 for idx, (tag, i1, i2, j1, j2) in enumerate(opcodes):
129 131 if idx == 0 and tag == 'equal': # Fixup leading unchanged block
130 132 i1, j1 = max(i1, i2 - n), max(j1, j2 - n)
131 133 elif tag == 'equal' and i2 - i1 > nn:
132 134 group.append((tag, i1, min(i2, i1 + n), j1,
133 135 min(j2, j1 + n)))
134 136 if not all_equal():
135 137 yield group
136 138 group = []
137 139 i1, j1 = max(i1, i2 - n), max(j1, j2 - n)
138 140 group.append((tag, i1, i2, j1, j2))
139 141
140 142 if group and not (len(group) == 1 and group[0][0] == 'equal'):
141 143 if group[-1][0] == 'equal': # Fixup trailing unchanged block
142 144 tag, i1, i2, j1, j2 = group[-1]
143 145 group[-1] = tag, i1, min(i2, i1 + n), j1, min(j2, j1 + n)
144 146 if not all_equal():
145 147 yield group
146 148 else:
147 149 for hunk in hunks:
148 150 yield hunk
149 151
150 152
151 153 NO_NEWLINE_AT_END = '\\ No newline at end of file'
152 154
153 155
154 156 def unified_diff(fromlines, tolines, context=None, ignore_blank_lines=0,
155 157 ignore_case=0, ignore_space_changes=0, lineterm='\n'):
156 158 """
157 159 Generator producing lines corresponding to a textual diff.
158 160
159 161 See `get_filtered_hunks` for the parameter descriptions.
160 162 """
161 163 # TODO: johbo: Check if this can be nicely integrated into the matching
162 164 if ignore_space_changes:
163 165 fromlines = [l.strip() for l in fromlines]
164 166 tolines = [l.strip() for l in tolines]
165 167
166 168 for group in get_filtered_hunks(fromlines, tolines, context,
167 169 ignore_blank_lines, ignore_case,
168 170 ignore_space_changes):
169 171 i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
170 172 if i1 == 0 and i2 == 0:
171 173 i1, i2 = -1, -1 # support for Add changes
172 174 if j1 == 0 and j2 == 0:
173 175 j1, j2 = -1, -1 # support for Delete changes
174 176 yield '@@ -%s +%s @@%s' % (
175 177 _hunk_range(i1 + 1, i2 - i1),
176 178 _hunk_range(j1 + 1, j2 - j1),
177 179 lineterm)
178 180 for tag, i1, i2, j1, j2 in group:
179 181 if tag == 'equal':
180 182 for line in fromlines[i1:i2]:
181 183 if not line.endswith(lineterm):
182 184 yield ' ' + line + lineterm
183 185 yield NO_NEWLINE_AT_END + lineterm
184 186 else:
185 187 yield ' ' + line
186 188 else:
187 189 if tag in ('replace', 'delete'):
188 190 for line in fromlines[i1:i2]:
189 191 if not line.endswith(lineterm):
190 192 yield '-' + line + lineterm
191 193 yield NO_NEWLINE_AT_END + lineterm
192 194 else:
193 195 yield '-' + line
194 196 if tag in ('replace', 'insert'):
195 197 for line in tolines[j1:j2]:
196 198 if not line.endswith(lineterm):
197 199 yield '+' + line + lineterm
198 200 yield NO_NEWLINE_AT_END + lineterm
199 201 else:
200 202 yield '+' + line
201 203
202 204
203 205 def _hunk_range(start, length):
204 206 if length != 1:
205 207 return '%d,%d' % (start, length)
206 208 else:
207 209 return '%d' % (start, )
General Comments 0
You need to be logged in to leave comments. Login now