Mercurial > public > mercurial-scm > hg
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 [], [], [], [], [], [], [] |