comparison mercurial/subrepo.py @ 18109:9e3910db4e78

subrepo: append subrepo path to subrepo error messages This change appends the subrepo path to subrepo errors. That is, when there is an error performing an operation a subrepo, rather than displaying a message such as: pushing subrepo MYSUBREPO to PATH searching for changes abort: push creates new remote head HEADHASH! hint: did you forget to merge? use push -f to force mercurial will show: pushing subrepo MYSUBREPO to PATH searching for changes abort: push creates new remote head HEADHASH! (in subrepo MYSUBREPO) hint: did you forget to merge? use push -f to force The rationale for this change is that the current error messages make it hard for TortoiseHg (and similar tools) to tell the user which subrepo caused the push failure. The "(in subrepo MYSUBREPO)" message has been added to those subrepo methods were it made sense (by using a decorator). We avoid appending "(in subrepo XXX)" multiple times when subrepos are nexted by throwing a "SubrepoAbort" exception after the extra message is appended. The decorator will then "ignore" (i.e. just re-raise) the exception and never add the message again. A small drawback of this method is that part of the exception trace is lost when the exception is catched and re-raised by the annotatesubrepoerror decorator. Also, because the state() function already printed the subrepo path when it threw an error, that error has been changed to avoid duplicating the subrepo path in the error message. Note that I have also updated several subrepo related tests to reflect these changes.
author Angel Ezquerra <angel.ezquerra@gmail.com>
date Thu, 13 Dec 2012 23:37:53 +0100
parents 54f063acc5ea
children 9aa6bee6e9f9
comparison
equal deleted inserted replaced
18108:bc694d78d843 18109:9e3910db4e78
11 import config, scmutil, util, node, error, cmdutil, bookmarks, match as matchmod 11 import config, scmutil, util, node, error, cmdutil, bookmarks, match as matchmod
12 hg = None 12 hg = None
13 propertycache = util.propertycache 13 propertycache = util.propertycache
14 14
15 nullstate = ('', '', 'empty') 15 nullstate = ('', '', 'empty')
16
17 class SubrepoAbort(error.Abort):
18 """Exception class used to avoid handling a subrepo error more than once"""
19
20 def annotatesubrepoerror(func):
21 def decoratedmethod(self, *args, **kargs):
22 try:
23 res = func(self, *args, **kargs)
24 except SubrepoAbort, ex:
25 # This exception has already been handled
26 raise ex
27 except error.Abort, ex:
28 errormsg = _('%s (in subrepo %s)') % (str(ex), subrelpath(self))
29 # avoid handling this exception by raising a SubrepoAbort exception
30 raise SubrepoAbort(errormsg, hint=ex.hint)
31 return res
32 return decoratedmethod
16 33
17 def state(ctx, ui): 34 def state(ctx, ui):
18 """return a state dict, mapping subrepo paths configured in .hgsub 35 """return a state dict, mapping subrepo paths configured in .hgsub
19 to tuple: (source from .hgsub, revision from .hgsubstate, kind 36 to tuple: (source from .hgsub, revision from .hgsubstate, kind
20 (key in types dict)) 37 (key in types dict))
242 if push and repo.ui.config('paths', 'default-push'): 259 if push and repo.ui.config('paths', 'default-push'):
243 return repo.ui.config('paths', 'default-push') 260 return repo.ui.config('paths', 'default-push')
244 if repo.ui.config('paths', 'default'): 261 if repo.ui.config('paths', 'default'):
245 return repo.ui.config('paths', 'default') 262 return repo.ui.config('paths', 'default')
246 if abort: 263 if abort:
247 raise util.Abort(_("default path for subrepository %s not found") % 264 raise util.Abort(_("default path for subrepository not found"))
248 reporelpath(repo))
249 265
250 def itersubrepos(ctx1, ctx2): 266 def itersubrepos(ctx1, ctx2):
251 """find subrepos in ctx1 or ctx2""" 267 """find subrepos in ctx1 or ctx2"""
252 # Create a (subpath, ctx) mapping where we prefer subpaths from 268 # Create a (subpath, ctx) mapping where we prefer subpaths from
253 # ctx1. The subpaths from ctx2 are important when the .hgsub file 269 # ctx1. The subpaths from ctx2 are important when the .hgsub file
400 v = r.ui.config(s, k) 416 v = r.ui.config(s, k)
401 if v: 417 if v:
402 self._repo.ui.setconfig(s, k, v) 418 self._repo.ui.setconfig(s, k, v)
403 self._initrepo(r, state[0], create) 419 self._initrepo(r, state[0], create)
404 420
421 @annotatesubrepoerror
405 def _initrepo(self, parentrepo, source, create): 422 def _initrepo(self, parentrepo, source, create):
406 self._repo._subparent = parentrepo 423 self._repo._subparent = parentrepo
407 self._repo._subsource = source 424 self._repo._subsource = source
408 425
409 if create: 426 if create:
420 addpathconfig('default', defpath) 437 addpathconfig('default', defpath)
421 if defpath != defpushpath: 438 if defpath != defpushpath:
422 addpathconfig('default-push', defpushpath) 439 addpathconfig('default-push', defpushpath)
423 fp.close() 440 fp.close()
424 441
442 @annotatesubrepoerror
425 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly): 443 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
426 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos, 444 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
427 os.path.join(prefix, self._path), explicitonly) 445 os.path.join(prefix, self._path), explicitonly)
428 446
447 @annotatesubrepoerror
429 def status(self, rev2, **opts): 448 def status(self, rev2, **opts):
430 try: 449 try:
431 rev1 = self._state[1] 450 rev1 = self._state[1]
432 ctx1 = self._repo[rev1] 451 ctx1 = self._repo[rev1]
433 ctx2 = self._repo[rev2] 452 ctx2 = self._repo[rev2]
435 except error.RepoLookupError, inst: 454 except error.RepoLookupError, inst:
436 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n') 455 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
437 % (inst, subrelpath(self))) 456 % (inst, subrelpath(self)))
438 return [], [], [], [], [], [], [] 457 return [], [], [], [], [], [], []
439 458
459 @annotatesubrepoerror
440 def diff(self, ui, diffopts, node2, match, prefix, **opts): 460 def diff(self, ui, diffopts, node2, match, prefix, **opts):
441 try: 461 try:
442 node1 = node.bin(self._state[1]) 462 node1 = node.bin(self._state[1])
443 # We currently expect node2 to come from substate and be 463 # We currently expect node2 to come from substate and be
444 # in hex format 464 # in hex format
450 listsubrepos=True, **opts) 470 listsubrepos=True, **opts)
451 except error.RepoLookupError, inst: 471 except error.RepoLookupError, inst:
452 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n') 472 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
453 % (inst, subrelpath(self))) 473 % (inst, subrelpath(self)))
454 474
475 @annotatesubrepoerror
455 def archive(self, ui, archiver, prefix, match=None): 476 def archive(self, ui, archiver, prefix, match=None):
456 self._get(self._state + ('hg',)) 477 self._get(self._state + ('hg',))
457 abstractsubrepo.archive(self, ui, archiver, prefix, match) 478 abstractsubrepo.archive(self, ui, archiver, prefix, match)
458 479
459 rev = self._state[1] 480 rev = self._state[1]
461 for subpath in ctx.substate: 482 for subpath in ctx.substate:
462 s = subrepo(ctx, subpath) 483 s = subrepo(ctx, subpath)
463 submatch = matchmod.narrowmatcher(subpath, match) 484 submatch = matchmod.narrowmatcher(subpath, match)
464 s.archive(ui, archiver, os.path.join(prefix, self._path), submatch) 485 s.archive(ui, archiver, os.path.join(prefix, self._path), submatch)
465 486
487 @annotatesubrepoerror
466 def dirty(self, ignoreupdate=False): 488 def dirty(self, ignoreupdate=False):
467 r = self._state[1] 489 r = self._state[1]
468 if r == '' and not ignoreupdate: # no state recorded 490 if r == '' and not ignoreupdate: # no state recorded
469 return True 491 return True
470 w = self._repo[None] 492 w = self._repo[None]
477 return self._repo['.'].hex() 499 return self._repo['.'].hex()
478 500
479 def checknested(self, path): 501 def checknested(self, path):
480 return self._repo._checknested(self._repo.wjoin(path)) 502 return self._repo._checknested(self._repo.wjoin(path))
481 503
504 @annotatesubrepoerror
482 def commit(self, text, user, date): 505 def commit(self, text, user, date):
483 # don't bother committing in the subrepo if it's only been 506 # don't bother committing in the subrepo if it's only been
484 # updated 507 # updated
485 if not self.dirty(True): 508 if not self.dirty(True):
486 return self._repo['.'].hex() 509 return self._repo['.'].hex()
488 n = self._repo.commit(text, user, date) 511 n = self._repo.commit(text, user, date)
489 if not n: 512 if not n:
490 return self._repo['.'].hex() # different version checked out 513 return self._repo['.'].hex() # different version checked out
491 return node.hex(n) 514 return node.hex(n)
492 515
516 @annotatesubrepoerror
493 def remove(self): 517 def remove(self):
494 # we can't fully delete the repository as it may contain 518 # we can't fully delete the repository as it may contain
495 # local-only history 519 # local-only history
496 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self)) 520 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
497 hg.clean(self._repo, node.nullid, False) 521 hg.clean(self._repo, node.nullid, False)
517 % (subrelpath(self), srcurl)) 541 % (subrelpath(self), srcurl))
518 self._repo.pull(other) 542 self._repo.pull(other)
519 bookmarks.updatefromremote(self._repo.ui, self._repo, other, 543 bookmarks.updatefromremote(self._repo.ui, self._repo, other,
520 srcurl) 544 srcurl)
521 545
546 @annotatesubrepoerror
522 def get(self, state, overwrite=False): 547 def get(self, state, overwrite=False):
523 self._get(state) 548 self._get(state)
524 source, revision, kind = state 549 source, revision, kind = state
525 self._repo.ui.debug("getting subrepo %s\n" % self._path) 550 self._repo.ui.debug("getting subrepo %s\n" % self._path)
526 hg.updaterepo(self._repo, revision, overwrite) 551 hg.updaterepo(self._repo, revision, overwrite)
527 552
553 @annotatesubrepoerror
528 def merge(self, state): 554 def merge(self, state):
529 self._get(state) 555 self._get(state)
530 cur = self._repo['.'] 556 cur = self._repo['.']
531 dst = self._repo[state[1]] 557 dst = self._repo[state[1]]
532 anc = dst.ancestor(cur) 558 anc = dst.ancestor(cur)
549 else: 575 else:
550 mergefunc() 576 mergefunc()
551 else: 577 else:
552 mergefunc() 578 mergefunc()
553 579
580 @annotatesubrepoerror
554 def push(self, opts): 581 def push(self, opts):
555 force = opts.get('force') 582 force = opts.get('force')
556 newbranch = opts.get('new_branch') 583 newbranch = opts.get('new_branch')
557 ssh = opts.get('ssh') 584 ssh = opts.get('ssh')
558 585
567 self._repo.ui.status(_('pushing subrepo %s to %s\n') % 594 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
568 (subrelpath(self), dsturl)) 595 (subrelpath(self), dsturl))
569 other = hg.peer(self._repo, {'ssh': ssh}, dsturl) 596 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
570 return self._repo.push(other, force, newbranch=newbranch) 597 return self._repo.push(other, force, newbranch=newbranch)
571 598
599 @annotatesubrepoerror
572 def outgoing(self, ui, dest, opts): 600 def outgoing(self, ui, dest, opts):
573 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts) 601 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
574 602
603 @annotatesubrepoerror
575 def incoming(self, ui, source, opts): 604 def incoming(self, ui, source, opts):
576 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts) 605 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
577 606
607 @annotatesubrepoerror
578 def files(self): 608 def files(self):
579 rev = self._state[1] 609 rev = self._state[1]
580 ctx = self._repo[rev] 610 ctx = self._repo[rev]
581 return ctx.manifest() 611 return ctx.manifest()
582 612
591 621
592 def walk(self, match): 622 def walk(self, match):
593 ctx = self._repo[None] 623 ctx = self._repo[None]
594 return ctx.walk(match) 624 return ctx.walk(match)
595 625
626 @annotatesubrepoerror
596 def forget(self, ui, match, prefix): 627 def forget(self, ui, match, prefix):
597 return cmdutil.forget(ui, self._repo, match, 628 return cmdutil.forget(ui, self._repo, match,
598 os.path.join(prefix, self._path), True) 629 os.path.join(prefix, self._path), True)
599 630
631 @annotatesubrepoerror
600 def revert(self, ui, substate, *pats, **opts): 632 def revert(self, ui, substate, *pats, **opts):
601 # reverting a subrepo is a 2 step process: 633 # reverting a subrepo is a 2 step process:
602 # 1. if the no_backup is not set, revert all modified 634 # 1. if the no_backup is not set, revert all modified
603 # files inside the subrepo 635 # files inside the subrepo
604 # 2. update the subrepo to the revision specified in 636 # 2. update the subrepo to the revision specified in
749 return lastrev 781 return lastrev
750 except error.Abort: 782 except error.Abort:
751 pass 783 pass
752 return rev 784 return rev
753 785
786 @annotatesubrepoerror
754 def commit(self, text, user, date): 787 def commit(self, text, user, date):
755 # user and date are out of our hands since svn is centralized 788 # user and date are out of our hands since svn is centralized
756 changed, extchanged, missing = self._wcchanged() 789 changed, extchanged, missing = self._wcchanged()
757 if not changed: 790 if not changed:
758 return self.basestate() 791 return self.basestate()
776 raise util.Abort(commitinfo.splitlines()[-1]) 809 raise util.Abort(commitinfo.splitlines()[-1])
777 newrev = newrev.groups()[0] 810 newrev = newrev.groups()[0]
778 self._ui.status(self._svncommand(['update', '-r', newrev])[0]) 811 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
779 return newrev 812 return newrev
780 813
814 @annotatesubrepoerror
781 def remove(self): 815 def remove(self):
782 if self.dirty(): 816 if self.dirty():
783 self._ui.warn(_('not removing repo %s because ' 817 self._ui.warn(_('not removing repo %s because '
784 'it has changes.\n' % self._path)) 818 'it has changes.\n' % self._path))
785 return 819 return
800 try: 834 try:
801 os.removedirs(os.path.dirname(path)) 835 os.removedirs(os.path.dirname(path))
802 except OSError: 836 except OSError:
803 pass 837 pass
804 838
839 @annotatesubrepoerror
805 def get(self, state, overwrite=False): 840 def get(self, state, overwrite=False):
806 if overwrite: 841 if overwrite:
807 self._svncommand(['revert', '--recursive']) 842 self._svncommand(['revert', '--recursive'])
808 args = ['checkout'] 843 args = ['checkout']
809 if self._svnversion >= (1, 5): 844 if self._svnversion >= (1, 5):
820 self.get(state, overwrite=False) 855 self.get(state, overwrite=False)
821 return 856 return
822 raise util.Abort((status or err).splitlines()[-1]) 857 raise util.Abort((status or err).splitlines()[-1])
823 self._ui.status(status) 858 self._ui.status(status)
824 859
860 @annotatesubrepoerror
825 def merge(self, state): 861 def merge(self, state):
826 old = self._state[1] 862 old = self._state[1]
827 new = state[1] 863 new = state[1]
828 wcrev = self._wcrev() 864 wcrev = self._wcrev()
829 if new != wcrev: 865 if new != wcrev:
833 869
834 def push(self, opts): 870 def push(self, opts):
835 # push is a no-op for SVN 871 # push is a no-op for SVN
836 return True 872 return True
837 873
874 @annotatesubrepoerror
838 def files(self): 875 def files(self):
839 output = self._svncommand(['list', '--recursive', '--xml'])[0] 876 output = self._svncommand(['list', '--recursive', '--xml'])[0]
840 doc = xml.dom.minidom.parseString(output) 877 doc = xml.dom.minidom.parseString(output)
841 paths = [] 878 paths = []
842 for e in doc.getElementsByTagName('entry'): 879 for e in doc.getElementsByTagName('entry'):
1019 self._gitcommand(['fetch']) 1056 self._gitcommand(['fetch'])
1020 if not self._githavelocally(revision): 1057 if not self._githavelocally(revision):
1021 raise util.Abort(_("revision %s does not exist in subrepo %s\n") % 1058 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1022 (revision, self._relpath)) 1059 (revision, self._relpath))
1023 1060
1061 @annotatesubrepoerror
1024 def dirty(self, ignoreupdate=False): 1062 def dirty(self, ignoreupdate=False):
1025 if self._gitmissing(): 1063 if self._gitmissing():
1026 return self._state[1] != '' 1064 return self._state[1] != ''
1027 if self._gitisbare(): 1065 if self._gitisbare():
1028 return True 1066 return True
1035 return code == 1 1073 return code == 1
1036 1074
1037 def basestate(self): 1075 def basestate(self):
1038 return self._gitstate() 1076 return self._gitstate()
1039 1077
1078 @annotatesubrepoerror
1040 def get(self, state, overwrite=False): 1079 def get(self, state, overwrite=False):
1041 source, revision, kind = state 1080 source, revision, kind = state
1042 if not revision: 1081 if not revision:
1043 self.remove() 1082 self.remove()
1044 return 1083 return
1118 self._gitcommand(['merge', '--ff', remote]) 1157 self._gitcommand(['merge', '--ff', remote])
1119 else: 1158 else:
1120 # a real merge would be required, just checkout the revision 1159 # a real merge would be required, just checkout the revision
1121 rawcheckout() 1160 rawcheckout()
1122 1161
1162 @annotatesubrepoerror
1123 def commit(self, text, user, date): 1163 def commit(self, text, user, date):
1124 if self._gitmissing(): 1164 if self._gitmissing():
1125 raise util.Abort(_("subrepo %s is missing") % self._relpath) 1165 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1126 cmd = ['commit', '-a', '-m', text] 1166 cmd = ['commit', '-a', '-m', text]
1127 env = os.environ.copy() 1167 env = os.environ.copy()
1135 self._gitcommand(cmd, env=env) 1175 self._gitcommand(cmd, env=env)
1136 # make sure commit works otherwise HEAD might not exist under certain 1176 # make sure commit works otherwise HEAD might not exist under certain
1137 # circumstances 1177 # circumstances
1138 return self._gitstate() 1178 return self._gitstate()
1139 1179
1180 @annotatesubrepoerror
1140 def merge(self, state): 1181 def merge(self, state):
1141 source, revision, kind = state 1182 source, revision, kind = state
1142 self._fetch(source, revision) 1183 self._fetch(source, revision)
1143 base = self._gitcommand(['merge-base', revision, self._state[1]]) 1184 base = self._gitcommand(['merge-base', revision, self._state[1]])
1144 self._gitupdatestat() 1185 self._gitupdatestat()
1157 self._state[1][:7], revision[:7]): 1198 self._state[1][:7], revision[:7]):
1158 mergefunc() 1199 mergefunc()
1159 else: 1200 else:
1160 mergefunc() 1201 mergefunc()
1161 1202
1203 @annotatesubrepoerror
1162 def push(self, opts): 1204 def push(self, opts):
1163 force = opts.get('force') 1205 force = opts.get('force')
1164 1206
1165 if not self._state[1]: 1207 if not self._state[1]:
1166 return True 1208 return True
1196 self._ui.warn(_('no branch checked out in subrepo %s\n' 1238 self._ui.warn(_('no branch checked out in subrepo %s\n'
1197 'cannot push revision %s\n') % 1239 'cannot push revision %s\n') %
1198 (self._relpath, self._state[1])) 1240 (self._relpath, self._state[1]))
1199 return False 1241 return False
1200 1242
1243 @annotatesubrepoerror
1201 def remove(self): 1244 def remove(self):
1202 if self._gitmissing(): 1245 if self._gitmissing():
1203 return 1246 return
1204 if self.dirty(): 1247 if self.dirty():
1205 self._ui.warn(_('not removing repo %s because ' 1248 self._ui.warn(_('not removing repo %s because '
1245 ui.progress(_('archiving (%s)') % relpath, i + 1, 1288 ui.progress(_('archiving (%s)') % relpath, i + 1,
1246 unit=_('files')) 1289 unit=_('files'))
1247 ui.progress(_('archiving (%s)') % relpath, None) 1290 ui.progress(_('archiving (%s)') % relpath, None)
1248 1291
1249 1292
1293 @annotatesubrepoerror
1250 def status(self, rev2, **opts): 1294 def status(self, rev2, **opts):
1251 rev1 = self._state[1] 1295 rev1 = self._state[1]
1252 if self._gitmissing() or not rev1: 1296 if self._gitmissing() or not rev1:
1253 # if the repo is missing, return no results 1297 # if the repo is missing, return no results
1254 return [], [], [], [], [], [], [] 1298 return [], [], [], [], [], [], []