Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/subrepo.py @ 24803:e89f909edffa stable 3.4-rc
merge default into stable for 3.4 freeze
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Thu, 16 Apr 2015 20:57:51 -0500 |
parents | 56e15db9109f |
children | a99931201d1b |
comparison
equal
deleted
inserted
replaced
24753:612ed41ae359 | 24803:e89f909edffa |
---|---|
4 # | 4 # |
5 # This software may be used and distributed according to the terms of the | 5 # This software may be used and distributed according to the terms of the |
6 # GNU General Public License version 2 or any later version. | 6 # GNU General Public License version 2 or any later version. |
7 | 7 |
8 import copy | 8 import copy |
9 import errno, os, re, shutil, posixpath, sys | 9 import errno, os, re, posixpath, sys |
10 import xml.dom.minidom | 10 import xml.dom.minidom |
11 import stat, subprocess, tarfile | 11 import stat, subprocess, tarfile |
12 from i18n import _ | 12 from i18n import _ |
13 import config, util, node, error, cmdutil, scmutil, match as matchmod | 13 import config, util, node, error, cmdutil, scmutil, match as matchmod |
14 import phases | 14 import phases |
68 data = ctx[f].data() | 68 data = ctx[f].data() |
69 except IOError, err: | 69 except IOError, err: |
70 if err.errno != errno.ENOENT: | 70 if err.errno != errno.ENOENT: |
71 raise | 71 raise |
72 # handle missing subrepo spec files as removed | 72 # handle missing subrepo spec files as removed |
73 ui.warn(_("warning: subrepo spec file %s not found\n") % f) | 73 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") % |
74 util.pathto(ctx.repo().root, ctx.repo().getcwd(), f)) | |
74 return | 75 return |
75 p.parse(f, data, sections, remap, read) | 76 p.parse(f, data, sections, remap, read) |
76 else: | 77 else: |
77 raise util.Abort(_("subrepo spec file %s not found") % f) | 78 repo = ctx.repo() |
79 raise util.Abort(_("subrepo spec file \'%s\' not found") % | |
80 util.pathto(repo.root, repo.getcwd(), f)) | |
78 | 81 |
79 if '.hgsub' in ctx: | 82 if '.hgsub' in ctx: |
80 read('.hgsub') | 83 read('.hgsub') |
81 | 84 |
82 for path, src in ui.configitems('subpaths'): | 85 for path, src in ui.configitems('subpaths'): |
90 if not l: | 93 if not l: |
91 continue | 94 continue |
92 try: | 95 try: |
93 revision, path = l.split(" ", 1) | 96 revision, path = l.split(" ", 1) |
94 except ValueError: | 97 except ValueError: |
98 repo = ctx.repo() | |
95 raise util.Abort(_("invalid subrepository revision " | 99 raise util.Abort(_("invalid subrepository revision " |
96 "specifier in .hgsubstate line %d") | 100 "specifier in \'%s\' line %d") |
97 % (i + 1)) | 101 % (util.pathto(repo.root, repo.getcwd(), |
102 '.hgsubstate'), (i + 1))) | |
98 rev[path] = revision | 103 rev[path] = revision |
99 except IOError, err: | 104 except IOError, err: |
100 if err.errno != errno.ENOENT: | 105 if err.errno != errno.ENOENT: |
101 raise | 106 raise |
102 | 107 |
125 kind, src = src.split(']', 1) | 130 kind, src = src.split(']', 1) |
126 kind = kind[1:] | 131 kind = kind[1:] |
127 src = src.lstrip() # strip any extra whitespace after ']' | 132 src = src.lstrip() # strip any extra whitespace after ']' |
128 | 133 |
129 if not util.url(src).isabs(): | 134 if not util.url(src).isabs(): |
130 parent = _abssource(ctx._repo, abort=False) | 135 parent = _abssource(ctx.repo(), abort=False) |
131 if parent: | 136 if parent: |
132 parent = util.url(parent) | 137 parent = util.url(parent) |
133 parent.path = posixpath.join(parent.path or '', src) | 138 parent.path = posixpath.join(parent.path or '', src) |
134 parent.path = posixpath.normpath(parent.path) | 139 parent.path = posixpath.normpath(parent.path) |
135 joined = str(parent) | 140 joined = str(parent) |
273 parent = parent._subparent | 278 parent = parent._subparent |
274 return repo.root[len(pathutil.normasprefix(parent.root)):] | 279 return repo.root[len(pathutil.normasprefix(parent.root)):] |
275 | 280 |
276 def subrelpath(sub): | 281 def subrelpath(sub): |
277 """return path to this subrepo as seen from outermost repo""" | 282 """return path to this subrepo as seen from outermost repo""" |
278 if util.safehasattr(sub, '_relpath'): | 283 return sub._relpath |
279 return sub._relpath | |
280 if not util.safehasattr(sub, '_repo'): | |
281 return sub._path | |
282 return reporelpath(sub._repo) | |
283 | 284 |
284 def _abssource(repo, push=False, abort=True): | 285 def _abssource(repo, push=False, abort=True): |
285 """return pull/push path of repo - either based on parent repo .hgsub info | 286 """return pull/push path of repo - either based on parent repo .hgsub info |
286 or on the top repo config. Abort or return None if no source found.""" | 287 or on the top repo config. Abort or return None if no source found.""" |
287 if util.safehasattr(repo, '_subparent'): | 288 if util.safehasattr(repo, '_subparent'): |
306 # chop off the .hg component to get the default path form | 307 # chop off the .hg component to get the default path form |
307 return os.path.dirname(repo.sharedpath) | 308 return os.path.dirname(repo.sharedpath) |
308 if abort: | 309 if abort: |
309 raise util.Abort(_("default path for subrepository not found")) | 310 raise util.Abort(_("default path for subrepository not found")) |
310 | 311 |
311 def _sanitize(ui, path, ignore): | 312 def _sanitize(ui, vfs, ignore): |
312 for dirname, dirs, names in os.walk(path): | 313 for dirname, dirs, names in vfs.walk(): |
313 for i, d in enumerate(dirs): | 314 for i, d in enumerate(dirs): |
314 if d.lower() == ignore: | 315 if d.lower() == ignore: |
315 del dirs[i] | 316 del dirs[i] |
316 break | 317 break |
317 if os.path.basename(dirname).lower() != '.hg': | 318 if os.path.basename(dirname).lower() != '.hg': |
318 continue | 319 continue |
319 for f in names: | 320 for f in names: |
320 if f.lower() == 'hgrc': | 321 if f.lower() == 'hgrc': |
321 ui.warn(_("warning: removing potentially hostile 'hgrc' " | 322 ui.warn(_("warning: removing potentially hostile 'hgrc' " |
322 "in '%s'\n") % dirname) | 323 "in '%s'\n") % vfs.join(dirname)) |
323 os.unlink(os.path.join(dirname, f)) | 324 vfs.unlink(vfs.reljoin(dirname, f)) |
324 | 325 |
325 def subrepo(ctx, path): | 326 def subrepo(ctx, path): |
326 """return instance of the right subrepo class for subrepo in path""" | 327 """return instance of the right subrepo class for subrepo in path""" |
327 # subrepo inherently violates our import layering rules | 328 # subrepo inherently violates our import layering rules |
328 # because it wants to make repo objects from deep inside the stack | 329 # because it wants to make repo objects from deep inside the stack |
330 # scripts that don't use our demand-loading | 331 # scripts that don't use our demand-loading |
331 global hg | 332 global hg |
332 import hg as h | 333 import hg as h |
333 hg = h | 334 hg = h |
334 | 335 |
335 pathutil.pathauditor(ctx._repo.root)(path) | 336 pathutil.pathauditor(ctx.repo().root)(path) |
336 state = ctx.substate[path] | 337 state = ctx.substate[path] |
337 if state[2] not in types: | 338 if state[2] not in types: |
338 raise util.Abort(_('unknown subrepo type %s') % state[2]) | 339 raise util.Abort(_('unknown subrepo type %s') % state[2]) |
339 return types[state[2]](ctx, path, state[:2]) | 340 return types[state[2]](ctx, path, state[:2]) |
340 | 341 |
371 | 372 |
372 # subrepo classes need to implement the following abstract class: | 373 # subrepo classes need to implement the following abstract class: |
373 | 374 |
374 class abstractsubrepo(object): | 375 class abstractsubrepo(object): |
375 | 376 |
376 def __init__(self, ui): | 377 def __init__(self, ctx, path): |
377 self.ui = ui | 378 """Initialize abstractsubrepo part |
379 | |
380 ``ctx`` is the context referring this subrepository in the | |
381 parent repository. | |
382 | |
383 ``path`` is the path to this subrepositiry as seen from | |
384 innermost repository. | |
385 """ | |
386 self.ui = ctx.repo().ui | |
387 self._ctx = ctx | |
388 self._path = path | |
378 | 389 |
379 def storeclean(self, path): | 390 def storeclean(self, path): |
380 """ | 391 """ |
381 returns true if the repository has not changed since it was last | 392 returns true if the repository has not changed since it was last |
382 cloned from or pushed to a given repository. | 393 cloned from or pushed to a given repository. |
388 match current stored state. If ignoreupdate is true, only check | 399 match current stored state. If ignoreupdate is true, only check |
389 whether the subrepo has uncommitted changes in its dirstate. | 400 whether the subrepo has uncommitted changes in its dirstate. |
390 """ | 401 """ |
391 raise NotImplementedError | 402 raise NotImplementedError |
392 | 403 |
404 def dirtyreason(self, ignoreupdate=False): | |
405 """return reason string if it is ``dirty()`` | |
406 | |
407 Returned string should have enough information for the message | |
408 of exception. | |
409 | |
410 This returns None, otherwise. | |
411 """ | |
412 if self.dirty(ignoreupdate=ignoreupdate): | |
413 return _("uncommitted changes in subrepository '%s'" | |
414 ) % subrelpath(self) | |
415 | |
416 def bailifchanged(self, ignoreupdate=False): | |
417 """raise Abort if subrepository is ``dirty()`` | |
418 """ | |
419 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate) | |
420 if dirtyreason: | |
421 raise util.Abort(dirtyreason) | |
422 | |
393 def basestate(self): | 423 def basestate(self): |
394 """current working directory base state, disregarding .hgsubstate | 424 """current working directory base state, disregarding .hgsubstate |
395 state and working directory modifications""" | 425 state and working directory modifications""" |
396 raise NotImplementedError | 426 raise NotImplementedError |
397 | 427 |
466 raise NotImplementedError | 496 raise NotImplementedError |
467 | 497 |
468 def fileflags(self, name): | 498 def fileflags(self, name): |
469 """return file flags""" | 499 """return file flags""" |
470 return '' | 500 return '' |
501 | |
502 def printfiles(self, ui, m, fm, fmt): | |
503 """handle the files command for this subrepo""" | |
504 return 1 | |
471 | 505 |
472 def archive(self, archiver, prefix, match=None): | 506 def archive(self, archiver, prefix, match=None): |
473 if match is not None: | 507 if match is not None: |
474 files = [f for f in self.files() if match(f)] | 508 files = [f for f in self.files() if match(f)] |
475 else: | 509 else: |
480 unit=_('files'), total=total) | 514 unit=_('files'), total=total) |
481 for i, name in enumerate(files): | 515 for i, name in enumerate(files): |
482 flags = self.fileflags(name) | 516 flags = self.fileflags(name) |
483 mode = 'x' in flags and 0755 or 0644 | 517 mode = 'x' in flags and 0755 or 0644 |
484 symlink = 'l' in flags | 518 symlink = 'l' in flags |
485 archiver.addfile(os.path.join(prefix, self._path, name), | 519 archiver.addfile(self.wvfs.reljoin(prefix, self._path, name), |
486 mode, symlink, self.filedata(name)) | 520 mode, symlink, self.filedata(name)) |
487 self.ui.progress(_('archiving (%s)') % relpath, i + 1, | 521 self.ui.progress(_('archiving (%s)') % relpath, i + 1, |
488 unit=_('files'), total=total) | 522 unit=_('files'), total=total) |
489 self.ui.progress(_('archiving (%s)') % relpath, None) | 523 self.ui.progress(_('archiving (%s)') % relpath, None) |
490 return total | 524 return total |
512 return [] | 546 return [] |
513 | 547 |
514 def shortid(self, revid): | 548 def shortid(self, revid): |
515 return revid | 549 return revid |
516 | 550 |
551 @propertycache | |
552 def wvfs(self): | |
553 """return vfs to access the working directory of this subrepository | |
554 """ | |
555 return scmutil.vfs(self._ctx.repo().wvfs.join(self._path)) | |
556 | |
557 @propertycache | |
558 def _relpath(self): | |
559 """return path to this subrepository as seen from outermost repository | |
560 """ | |
561 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path) | |
562 | |
517 class hgsubrepo(abstractsubrepo): | 563 class hgsubrepo(abstractsubrepo): |
518 def __init__(self, ctx, path, state): | 564 def __init__(self, ctx, path, state): |
519 super(hgsubrepo, self).__init__(ctx._repo.ui) | 565 super(hgsubrepo, self).__init__(ctx, path) |
520 self._path = path | |
521 self._state = state | 566 self._state = state |
522 r = ctx._repo | 567 r = ctx.repo() |
523 root = r.wjoin(path) | 568 root = r.wjoin(path) |
524 create = not r.wvfs.exists('%s/.hg' % path) | 569 create = not r.wvfs.exists('%s/.hg' % path) |
525 self._repo = hg.repository(r.baseui, root, create=create) | 570 self._repo = hg.repository(r.baseui, root, create=create) |
526 self.ui = self._repo.ui | 571 self.ui = self._repo.ui |
527 for s, k in [('ui', 'commitsubrepos')]: | 572 for s, k in [('ui', 'commitsubrepos')]: |
621 fp.close() | 666 fp.close() |
622 | 667 |
623 @annotatesubrepoerror | 668 @annotatesubrepoerror |
624 def add(self, ui, match, prefix, explicitonly, **opts): | 669 def add(self, ui, match, prefix, explicitonly, **opts): |
625 return cmdutil.add(ui, self._repo, match, | 670 return cmdutil.add(ui, self._repo, match, |
626 os.path.join(prefix, self._path), explicitonly, | 671 self.wvfs.reljoin(prefix, self._path), |
627 **opts) | 672 explicitonly, **opts) |
628 | 673 |
674 @annotatesubrepoerror | |
629 def addremove(self, m, prefix, opts, dry_run, similarity): | 675 def addremove(self, m, prefix, opts, dry_run, similarity): |
630 # In the same way as sub directories are processed, once in a subrepo, | 676 # In the same way as sub directories are processed, once in a subrepo, |
631 # always entry any of its subrepos. Don't corrupt the options that will | 677 # always entry any of its subrepos. Don't corrupt the options that will |
632 # be used to process sibling subrepos however. | 678 # be used to process sibling subrepos however. |
633 opts = copy.copy(opts) | 679 opts = copy.copy(opts) |
634 opts['subrepos'] = True | 680 opts['subrepos'] = True |
635 return scmutil.addremove(self._repo, m, | 681 return scmutil.addremove(self._repo, m, |
636 os.path.join(prefix, self._path), opts, | 682 self.wvfs.reljoin(prefix, self._path), opts, |
637 dry_run, similarity) | 683 dry_run, similarity) |
638 | 684 |
639 @annotatesubrepoerror | 685 @annotatesubrepoerror |
640 def cat(self, match, prefix, **opts): | 686 def cat(self, match, prefix, **opts): |
641 rev = self._state[1] | 687 rev = self._state[1] |
678 ctx = self._repo[rev] | 724 ctx = self._repo[rev] |
679 for subpath in ctx.substate: | 725 for subpath in ctx.substate: |
680 s = subrepo(ctx, subpath) | 726 s = subrepo(ctx, subpath) |
681 submatch = matchmod.narrowmatcher(subpath, match) | 727 submatch = matchmod.narrowmatcher(subpath, match) |
682 total += s.archive( | 728 total += s.archive( |
683 archiver, os.path.join(prefix, self._path), submatch) | 729 archiver, self.wvfs.reljoin(prefix, self._path), submatch) |
684 return total | 730 return total |
685 | 731 |
686 @annotatesubrepoerror | 732 @annotatesubrepoerror |
687 def dirty(self, ignoreupdate=False): | 733 def dirty(self, ignoreupdate=False): |
688 r = self._state[1] | 734 r = self._state[1] |
732 other = hg.peer(self._repo, {}, srcurl) | 778 other = hg.peer(self._repo, {}, srcurl) |
733 if len(self._repo) == 0: | 779 if len(self._repo) == 0: |
734 self.ui.status(_('cloning subrepo %s from %s\n') | 780 self.ui.status(_('cloning subrepo %s from %s\n') |
735 % (subrelpath(self), srcurl)) | 781 % (subrelpath(self), srcurl)) |
736 parentrepo = self._repo._subparent | 782 parentrepo = self._repo._subparent |
737 shutil.rmtree(self._repo.path) | 783 # use self._repo.vfs instead of self.wvfs to remove .hg only |
784 self._repo.vfs.rmtree() | |
738 other, cloned = hg.clone(self._repo._subparent.baseui, {}, | 785 other, cloned = hg.clone(self._repo._subparent.baseui, {}, |
739 other, self._repo.root, | 786 other, self._repo.root, |
740 update=False) | 787 update=False) |
741 self._repo = cloned.local() | 788 self._repo = cloned.local() |
742 self._initrepo(parentrepo, source, create=True) | 789 self._initrepo(parentrepo, source, create=True) |
833 | 880 |
834 @annotatesubrepoerror | 881 @annotatesubrepoerror |
835 def files(self): | 882 def files(self): |
836 rev = self._state[1] | 883 rev = self._state[1] |
837 ctx = self._repo[rev] | 884 ctx = self._repo[rev] |
838 return ctx.manifest() | 885 return ctx.manifest().keys() |
839 | 886 |
840 def filedata(self, name): | 887 def filedata(self, name): |
841 rev = self._state[1] | 888 rev = self._state[1] |
842 return self._repo[rev][name].data() | 889 return self._repo[rev][name].data() |
843 | 890 |
844 def fileflags(self, name): | 891 def fileflags(self, name): |
845 rev = self._state[1] | 892 rev = self._state[1] |
846 ctx = self._repo[rev] | 893 ctx = self._repo[rev] |
847 return ctx.flags(name) | 894 return ctx.flags(name) |
848 | 895 |
896 @annotatesubrepoerror | |
897 def printfiles(self, ui, m, fm, fmt): | |
898 # If the parent context is a workingctx, use the workingctx here for | |
899 # consistency. | |
900 if self._ctx.rev() is None: | |
901 ctx = self._repo[None] | |
902 else: | |
903 rev = self._state[1] | |
904 ctx = self._repo[rev] | |
905 return cmdutil.files(ui, ctx, m, fm, fmt, True) | |
906 | |
849 def walk(self, match): | 907 def walk(self, match): |
850 ctx = self._repo[None] | 908 ctx = self._repo[None] |
851 return ctx.walk(match) | 909 return ctx.walk(match) |
852 | 910 |
853 @annotatesubrepoerror | 911 @annotatesubrepoerror |
854 def forget(self, match, prefix): | 912 def forget(self, match, prefix): |
855 return cmdutil.forget(self.ui, self._repo, match, | 913 return cmdutil.forget(self.ui, self._repo, match, |
856 os.path.join(prefix, self._path), True) | 914 self.wvfs.reljoin(prefix, self._path), True) |
857 | 915 |
858 @annotatesubrepoerror | 916 @annotatesubrepoerror |
859 def removefiles(self, matcher, prefix, after, force, subrepos): | 917 def removefiles(self, matcher, prefix, after, force, subrepos): |
860 return cmdutil.remove(self.ui, self._repo, matcher, | 918 return cmdutil.remove(self.ui, self._repo, matcher, |
861 os.path.join(prefix, self._path), after, force, | 919 self.wvfs.reljoin(prefix, self._path), |
862 subrepos) | 920 after, force, subrepos) |
863 | 921 |
864 @annotatesubrepoerror | 922 @annotatesubrepoerror |
865 def revert(self, substate, *pats, **opts): | 923 def revert(self, substate, *pats, **opts): |
866 # reverting a subrepo is a 2 step process: | 924 # reverting a subrepo is a 2 step process: |
867 # 1. if the no_backup is not set, revert all modified | 925 # 1. if the no_backup is not set, revert all modified |
875 # We could do it if there was a set:subrepos() predicate | 933 # We could do it if there was a set:subrepos() predicate |
876 opts = opts.copy() | 934 opts = opts.copy() |
877 opts['date'] = None | 935 opts['date'] = None |
878 opts['rev'] = substate[1] | 936 opts['rev'] = substate[1] |
879 | 937 |
880 pats = [] | |
881 if not opts.get('all'): | |
882 pats = ['set:modified()'] | |
883 self.filerevert(*pats, **opts) | 938 self.filerevert(*pats, **opts) |
884 | 939 |
885 # Update the repo to the revision specified in the given substate | 940 # Update the repo to the revision specified in the given substate |
886 self.get(substate, overwrite=True) | 941 if not opts.get('dry_run'): |
942 self.get(substate, overwrite=True) | |
887 | 943 |
888 def filerevert(self, *pats, **opts): | 944 def filerevert(self, *pats, **opts): |
889 ctx = self._repo[opts['rev']] | 945 ctx = self._repo[opts['rev']] |
890 parents = self._repo.dirstate.parents() | 946 parents = self._repo.dirstate.parents() |
891 if opts.get('all'): | 947 if opts.get('all'): |
895 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts) | 951 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts) |
896 | 952 |
897 def shortid(self, revid): | 953 def shortid(self, revid): |
898 return revid[:12] | 954 return revid[:12] |
899 | 955 |
956 @propertycache | |
957 def wvfs(self): | |
958 """return own wvfs for efficiency and consitency | |
959 """ | |
960 return self._repo.wvfs | |
961 | |
962 @propertycache | |
963 def _relpath(self): | |
964 """return path to this subrepository as seen from outermost repository | |
965 """ | |
966 # Keep consistent dir separators by avoiding vfs.join(self._path) | |
967 return reporelpath(self._repo) | |
968 | |
900 class svnsubrepo(abstractsubrepo): | 969 class svnsubrepo(abstractsubrepo): |
901 def __init__(self, ctx, path, state): | 970 def __init__(self, ctx, path, state): |
902 super(svnsubrepo, self).__init__(ctx._repo.ui) | 971 super(svnsubrepo, self).__init__(ctx, path) |
903 self._path = path | |
904 self._state = state | 972 self._state = state |
905 self._ctx = ctx | |
906 self._exe = util.findexe('svn') | 973 self._exe = util.findexe('svn') |
907 if not self._exe: | 974 if not self._exe: |
908 raise util.Abort(_("'svn' executable not found for subrepo '%s'") | 975 raise util.Abort(_("'svn' executable not found for subrepo '%s'") |
909 % self._path) | 976 % self._path) |
910 | 977 |
921 # --non-interactive. | 988 # --non-interactive. |
922 if commands[0] in ('update', 'checkout', 'commit'): | 989 if commands[0] in ('update', 'checkout', 'commit'): |
923 cmd.append('--non-interactive') | 990 cmd.append('--non-interactive') |
924 cmd.extend(commands) | 991 cmd.extend(commands) |
925 if filename is not None: | 992 if filename is not None: |
926 path = os.path.join(self._ctx._repo.origroot, self._path, filename) | 993 path = self.wvfs.reljoin(self._ctx.repo().origroot, |
994 self._path, filename) | |
927 cmd.append(path) | 995 cmd.append(path) |
928 env = dict(os.environ) | 996 env = dict(os.environ) |
929 # Avoid localized output, preserve current locale for everything else. | 997 # Avoid localized output, preserve current locale for everything else. |
930 lc_all = env.get('LC_ALL') | 998 lc_all = env.get('LC_ALL') |
931 if lc_all: | 999 if lc_all: |
1053 self.ui.warn(_('not removing repo %s because ' | 1121 self.ui.warn(_('not removing repo %s because ' |
1054 'it has changes.\n') % self._path) | 1122 'it has changes.\n') % self._path) |
1055 return | 1123 return |
1056 self.ui.note(_('removing subrepo %s\n') % self._path) | 1124 self.ui.note(_('removing subrepo %s\n') % self._path) |
1057 | 1125 |
1058 def onerror(function, path, excinfo): | 1126 self.wvfs.rmtree(forcibly=True) |
1059 if function is not os.remove: | |
1060 raise | |
1061 # read-only files cannot be unlinked under Windows | |
1062 s = os.stat(path) | |
1063 if (s.st_mode & stat.S_IWRITE) != 0: | |
1064 raise | |
1065 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE) | |
1066 os.remove(path) | |
1067 | |
1068 path = self._ctx._repo.wjoin(self._path) | |
1069 shutil.rmtree(path, onerror=onerror) | |
1070 try: | 1127 try: |
1071 os.removedirs(os.path.dirname(path)) | 1128 self._ctx.repo().wvfs.removedirs(os.path.dirname(self._path)) |
1072 except OSError: | 1129 except OSError: |
1073 pass | 1130 pass |
1074 | 1131 |
1075 @annotatesubrepoerror | 1132 @annotatesubrepoerror |
1076 def get(self, state, overwrite=False): | 1133 def get(self, state, overwrite=False): |
1081 args.append('--force') | 1138 args.append('--force') |
1082 # The revision must be specified at the end of the URL to properly | 1139 # The revision must be specified at the end of the URL to properly |
1083 # update to a directory which has since been deleted and recreated. | 1140 # update to a directory which has since been deleted and recreated. |
1084 args.append('%s@%s' % (state[0], state[1])) | 1141 args.append('%s@%s' % (state[0], state[1])) |
1085 status, err = self._svncommand(args, failok=True) | 1142 status, err = self._svncommand(args, failok=True) |
1086 _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn') | 1143 _sanitize(self.ui, self.wvfs, '.svn') |
1087 if not re.search('Checked out revision [0-9]+.', status): | 1144 if not re.search('Checked out revision [0-9]+.', status): |
1088 if ('is already a working copy for a different URL' in err | 1145 if ('is already a working copy for a different URL' in err |
1089 and (self._wcchanged()[:2] == (False, False))): | 1146 and (self._wcchanged()[:2] == (False, False))): |
1090 # obstructed but clean working copy, so just blow it away. | 1147 # obstructed but clean working copy, so just blow it away. |
1091 self.remove() | 1148 self.remove() |
1127 return self._svncommand(['cat'], name)[0] | 1184 return self._svncommand(['cat'], name)[0] |
1128 | 1185 |
1129 | 1186 |
1130 class gitsubrepo(abstractsubrepo): | 1187 class gitsubrepo(abstractsubrepo): |
1131 def __init__(self, ctx, path, state): | 1188 def __init__(self, ctx, path, state): |
1132 super(gitsubrepo, self).__init__(ctx._repo.ui) | 1189 super(gitsubrepo, self).__init__(ctx, path) |
1133 self._state = state | 1190 self._state = state |
1134 self._ctx = ctx | 1191 self._abspath = ctx.repo().wjoin(path) |
1135 self._path = path | 1192 self._subparent = ctx.repo() |
1136 self._relpath = os.path.join(reporelpath(ctx._repo), path) | |
1137 self._abspath = ctx._repo.wjoin(path) | |
1138 self._subparent = ctx._repo | |
1139 self._ensuregit() | 1193 self._ensuregit() |
1140 | 1194 |
1141 def _ensuregit(self): | 1195 def _ensuregit(self): |
1142 try: | 1196 try: |
1143 self._gitexecutable = 'git' | 1197 self._gitexecutable = 'git' |
1242 (command, p.returncode, self._relpath)) | 1296 (command, p.returncode, self._relpath)) |
1243 | 1297 |
1244 return retdata, p.returncode | 1298 return retdata, p.returncode |
1245 | 1299 |
1246 def _gitmissing(self): | 1300 def _gitmissing(self): |
1247 return not os.path.exists(os.path.join(self._abspath, '.git')) | 1301 return not self.wvfs.exists('.git') |
1248 | 1302 |
1249 def _gitstate(self): | 1303 def _gitstate(self): |
1250 return self._gitcommand(['rev-parse', 'HEAD']) | 1304 return self._gitcommand(['rev-parse', 'HEAD']) |
1251 | 1305 |
1252 def _gitcurrentbranch(self): | 1306 def _gitcurrentbranch(self): |
1385 # the -f option will otherwise throw away files added for | 1439 # the -f option will otherwise throw away files added for |
1386 # commit, not just unmark them. | 1440 # commit, not just unmark them. |
1387 self._gitcommand(['reset', 'HEAD']) | 1441 self._gitcommand(['reset', 'HEAD']) |
1388 cmd.append('-f') | 1442 cmd.append('-f') |
1389 self._gitcommand(cmd + args) | 1443 self._gitcommand(cmd + args) |
1390 _sanitize(self.ui, self._abspath, '.git') | 1444 _sanitize(self.ui, self.wvfs, '.git') |
1391 | 1445 |
1392 def rawcheckout(): | 1446 def rawcheckout(): |
1393 # no branch to checkout, check it out with no branch | 1447 # no branch to checkout, check it out with no branch |
1394 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') % | 1448 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') % |
1395 self._relpath) | 1449 self._relpath) |
1434 # Since we are only looking at branching at update, we need to | 1488 # Since we are only looking at branching at update, we need to |
1435 # detect this situation and perform this action lazily. | 1489 # detect this situation and perform this action lazily. |
1436 if tracking[remote] != self._gitcurrentbranch(): | 1490 if tracking[remote] != self._gitcurrentbranch(): |
1437 checkout([tracking[remote]]) | 1491 checkout([tracking[remote]]) |
1438 self._gitcommand(['merge', '--ff', remote]) | 1492 self._gitcommand(['merge', '--ff', remote]) |
1439 _sanitize(self.ui, self._abspath, '.git') | 1493 _sanitize(self.ui, self.wvfs, '.git') |
1440 else: | 1494 else: |
1441 # a real merge would be required, just checkout the revision | 1495 # a real merge would be required, just checkout the revision |
1442 rawcheckout() | 1496 rawcheckout() |
1443 | 1497 |
1444 @annotatesubrepoerror | 1498 @annotatesubrepoerror |
1470 def mergefunc(): | 1524 def mergefunc(): |
1471 if base == revision: | 1525 if base == revision: |
1472 self.get(state) # fast forward merge | 1526 self.get(state) # fast forward merge |
1473 elif base != self._state[1]: | 1527 elif base != self._state[1]: |
1474 self._gitcommand(['merge', '--no-commit', revision]) | 1528 self._gitcommand(['merge', '--no-commit', revision]) |
1475 _sanitize(self.ui, self._abspath, '.git') | 1529 _sanitize(self.ui, self.wvfs, '.git') |
1476 | 1530 |
1477 if self.dirty(): | 1531 if self.dirty(): |
1478 if self._gitstate() != revision: | 1532 if self._gitstate() != revision: |
1479 dirty = self._gitstate() == self._state[1] or code != 0 | 1533 dirty = self._gitstate() == self._state[1] or code != 0 |
1480 if _updateprompt(self.ui, self, dirty, | 1534 if _updateprompt(self.ui, self, dirty, |
1522 'cannot push revision %s\n') % | 1576 'cannot push revision %s\n') % |
1523 (self._relpath, self._state[1])) | 1577 (self._relpath, self._state[1])) |
1524 return False | 1578 return False |
1525 | 1579 |
1526 @annotatesubrepoerror | 1580 @annotatesubrepoerror |
1581 def add(self, ui, match, prefix, explicitonly, **opts): | |
1582 if self._gitmissing(): | |
1583 return [] | |
1584 | |
1585 (modified, added, removed, | |
1586 deleted, unknown, ignored, clean) = self.status(None, unknown=True, | |
1587 clean=True) | |
1588 | |
1589 tracked = set() | |
1590 # dirstates 'amn' warn, 'r' is added again | |
1591 for l in (modified, added, deleted, clean): | |
1592 tracked.update(l) | |
1593 | |
1594 # Unknown files not of interest will be rejected by the matcher | |
1595 files = unknown | |
1596 files.extend(match.files()) | |
1597 | |
1598 rejected = [] | |
1599 | |
1600 files = [f for f in sorted(set(files)) if match(f)] | |
1601 for f in files: | |
1602 exact = match.exact(f) | |
1603 command = ["add"] | |
1604 if exact: | |
1605 command.append("-f") #should be added, even if ignored | |
1606 if ui.verbose or not exact: | |
1607 ui.status(_('adding %s\n') % match.rel(f)) | |
1608 | |
1609 if f in tracked: # hg prints 'adding' even if already tracked | |
1610 if exact: | |
1611 rejected.append(f) | |
1612 continue | |
1613 if not opts.get('dry_run'): | |
1614 self._gitcommand(command + [f]) | |
1615 | |
1616 for f in rejected: | |
1617 ui.warn(_("%s already tracked!\n") % match.abs(f)) | |
1618 | |
1619 return rejected | |
1620 | |
1621 @annotatesubrepoerror | |
1527 def remove(self): | 1622 def remove(self): |
1528 if self._gitmissing(): | 1623 if self._gitmissing(): |
1529 return | 1624 return |
1530 if self.dirty(): | 1625 if self.dirty(): |
1531 self.ui.warn(_('not removing repo %s because ' | 1626 self.ui.warn(_('not removing repo %s because ' |
1533 return | 1628 return |
1534 # we can't fully delete the repository as it may contain | 1629 # we can't fully delete the repository as it may contain |
1535 # local-only history | 1630 # local-only history |
1536 self.ui.note(_('removing subrepo %s\n') % self._relpath) | 1631 self.ui.note(_('removing subrepo %s\n') % self._relpath) |
1537 self._gitcommand(['config', 'core.bare', 'true']) | 1632 self._gitcommand(['config', 'core.bare', 'true']) |
1538 for f in os.listdir(self._abspath): | 1633 for f, kind in self.wvfs.readdir(): |
1539 if f == '.git': | 1634 if f == '.git': |
1540 continue | 1635 continue |
1541 path = os.path.join(self._abspath, f) | 1636 if kind == stat.S_IFDIR: |
1542 if os.path.isdir(path) and not os.path.islink(path): | 1637 self.wvfs.rmtree(f) |
1543 shutil.rmtree(path) | |
1544 else: | 1638 else: |
1545 os.remove(path) | 1639 self.wvfs.unlink(f) |
1546 | 1640 |
1547 def archive(self, archiver, prefix, match=None): | 1641 def archive(self, archiver, prefix, match=None): |
1548 total = 0 | 1642 total = 0 |
1549 source, revision = self._state | 1643 source, revision = self._state |
1550 if not revision: | 1644 if not revision: |
1565 continue | 1659 continue |
1566 if info.issym(): | 1660 if info.issym(): |
1567 data = info.linkname | 1661 data = info.linkname |
1568 else: | 1662 else: |
1569 data = tar.extractfile(info).read() | 1663 data = tar.extractfile(info).read() |
1570 archiver.addfile(os.path.join(prefix, self._path, info.name), | 1664 archiver.addfile(self.wvfs.reljoin(prefix, self._path, info.name), |
1571 info.mode, info.issym(), data) | 1665 info.mode, info.issym(), data) |
1572 total += 1 | 1666 total += 1 |
1573 self.ui.progress(_('archiving (%s)') % relpath, i + 1, | 1667 self.ui.progress(_('archiving (%s)') % relpath, i + 1, |
1574 unit=_('files')) | 1668 unit=_('files')) |
1575 self.ui.progress(_('archiving (%s)') % relpath, None) | 1669 self.ui.progress(_('archiving (%s)') % relpath, None) |
1576 return total | 1670 return total |
1577 | 1671 |
1578 | 1672 |
1579 @annotatesubrepoerror | 1673 @annotatesubrepoerror |
1674 def cat(self, match, prefix, **opts): | |
1675 rev = self._state[1] | |
1676 if match.anypats(): | |
1677 return 1 #No support for include/exclude yet | |
1678 | |
1679 if not match.files(): | |
1680 return 1 | |
1681 | |
1682 for f in match.files(): | |
1683 output = self._gitcommand(["show", "%s:%s" % (rev, f)]) | |
1684 fp = cmdutil.makefileobj(self._subparent, opts.get('output'), | |
1685 self._ctx.node(), | |
1686 pathname=self.wvfs.reljoin(prefix, f)) | |
1687 fp.write(output) | |
1688 fp.close() | |
1689 return 0 | |
1690 | |
1691 | |
1692 @annotatesubrepoerror | |
1580 def status(self, rev2, **opts): | 1693 def status(self, rev2, **opts): |
1581 rev1 = self._state[1] | 1694 rev1 = self._state[1] |
1582 if self._gitmissing() or not rev1: | 1695 if self._gitmissing() or not rev1: |
1583 # if the repo is missing, return no results | 1696 # if the repo is missing, return no results |
1584 return [], [], [], [], [], [], [] | 1697 return scmutil.status([], [], [], [], [], [], []) |
1585 modified, added, removed = [], [], [] | 1698 modified, added, removed = [], [], [] |
1586 self._gitupdatestat() | 1699 self._gitupdatestat() |
1587 if rev2: | 1700 if rev2: |
1588 command = ['diff-tree', rev1, rev2] | 1701 command = ['diff-tree', rev1, rev2] |
1589 else: | 1702 else: |
1601 elif status == 'D': | 1714 elif status == 'D': |
1602 removed.append(f) | 1715 removed.append(f) |
1603 | 1716 |
1604 deleted, unknown, ignored, clean = [], [], [], [] | 1717 deleted, unknown, ignored, clean = [], [], [], [] |
1605 | 1718 |
1606 if not rev2: | 1719 command = ['status', '--porcelain', '-z'] |
1607 command = ['ls-files', '--others', '--exclude-standard'] | 1720 if opts.get('unknown'): |
1608 out = self._gitcommand(command) | 1721 command += ['--untracked-files=all'] |
1609 for line in out.split('\n'): | 1722 if opts.get('ignored'): |
1610 if len(line) == 0: | 1723 command += ['--ignored'] |
1611 continue | 1724 out = self._gitcommand(command) |
1612 unknown.append(line) | 1725 |
1726 changedfiles = set() | |
1727 changedfiles.update(modified) | |
1728 changedfiles.update(added) | |
1729 changedfiles.update(removed) | |
1730 for line in out.split('\0'): | |
1731 if not line: | |
1732 continue | |
1733 st = line[0:2] | |
1734 #moves and copies show 2 files on one line | |
1735 if line.find('\0') >= 0: | |
1736 filename1, filename2 = line[3:].split('\0') | |
1737 else: | |
1738 filename1 = line[3:] | |
1739 filename2 = None | |
1740 | |
1741 changedfiles.add(filename1) | |
1742 if filename2: | |
1743 changedfiles.add(filename2) | |
1744 | |
1745 if st == '??': | |
1746 unknown.append(filename1) | |
1747 elif st == '!!': | |
1748 ignored.append(filename1) | |
1749 | |
1750 if opts.get('clean'): | |
1751 out = self._gitcommand(['ls-files']) | |
1752 for f in out.split('\n'): | |
1753 if not f in changedfiles: | |
1754 clean.append(f) | |
1613 | 1755 |
1614 return scmutil.status(modified, added, removed, deleted, | 1756 return scmutil.status(modified, added, removed, deleted, |
1615 unknown, ignored, clean) | 1757 unknown, ignored, clean) |
1616 | 1758 |
1617 @annotatesubrepoerror | 1759 @annotatesubrepoerror |
1622 cmd.append('--stat') | 1764 cmd.append('--stat') |
1623 else: | 1765 else: |
1624 # for Git, this also implies '-p' | 1766 # for Git, this also implies '-p' |
1625 cmd.append('-U%d' % diffopts.context) | 1767 cmd.append('-U%d' % diffopts.context) |
1626 | 1768 |
1627 gitprefix = os.path.join(prefix, self._path) | 1769 gitprefix = self.wvfs.reljoin(prefix, self._path) |
1628 | 1770 |
1629 if diffopts.noprefix: | 1771 if diffopts.noprefix: |
1630 cmd.extend(['--src-prefix=%s/' % gitprefix, | 1772 cmd.extend(['--src-prefix=%s/' % gitprefix, |
1631 '--dst-prefix=%s/' % gitprefix]) | 1773 '--dst-prefix=%s/' % gitprefix]) |
1632 else: | 1774 else: |
1643 | 1785 |
1644 cmd.append(node1) | 1786 cmd.append(node1) |
1645 if node2: | 1787 if node2: |
1646 cmd.append(node2) | 1788 cmd.append(node2) |
1647 | 1789 |
1648 if match.anypats(): | |
1649 return #No support for include/exclude yet | |
1650 | |
1651 output = "" | 1790 output = "" |
1652 if match.always(): | 1791 if match.always(): |
1653 output += self._gitcommand(cmd) + '\n' | 1792 output += self._gitcommand(cmd) + '\n' |
1654 elif match.files(): | 1793 else: |
1655 for f in match.files(): | 1794 st = self.status(node2)[:3] |
1656 output += self._gitcommand(cmd + [f]) + '\n' | 1795 files = [f for sublist in st for f in sublist] |
1657 elif match(gitprefix): #Subrepo is matched | 1796 for f in files: |
1658 output += self._gitcommand(cmd) + '\n' | 1797 if match(f): |
1798 output += self._gitcommand(cmd + ['--', f]) + '\n' | |
1659 | 1799 |
1660 if output.strip(): | 1800 if output.strip(): |
1661 ui.write(output) | 1801 ui.write(output) |
1662 | 1802 |
1663 @annotatesubrepoerror | 1803 @annotatesubrepoerror |
1668 names = status.modified | 1808 names = status.modified |
1669 for name in names: | 1809 for name in names: |
1670 bakname = "%s.orig" % name | 1810 bakname = "%s.orig" % name |
1671 self.ui.note(_('saving current version of %s as %s\n') % | 1811 self.ui.note(_('saving current version of %s as %s\n') % |
1672 (name, bakname)) | 1812 (name, bakname)) |
1673 util.rename(os.path.join(self._abspath, name), | 1813 self.wvfs.rename(name, bakname) |
1674 os.path.join(self._abspath, bakname)) | 1814 |
1675 | 1815 if not opts.get('dry_run'): |
1676 self.get(substate, overwrite=True) | 1816 self.get(substate, overwrite=True) |
1677 return [] | 1817 return [] |
1678 | 1818 |
1679 def shortid(self, revid): | 1819 def shortid(self, revid): |
1680 return revid[:7] | 1820 return revid[:7] |
1681 | 1821 |