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) |
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() |
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 [], [], [], [], [], [], [] |