eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/subrepo.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # subrepo.py - sub-repository handling for Mercurial
       
     2 #
       
     3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
       
     4 #
       
     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.
       
     7 
       
     8 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
       
     9 import stat, subprocess
       
    10 from i18n import _
       
    11 import config, util, node, error, cmdutil
       
    12 hg = None
       
    13 
       
    14 nullstate = ('', '', 'empty')
       
    15 
       
    16 def state(ctx, ui):
       
    17     """return a state dict, mapping subrepo paths configured in .hgsub
       
    18     to tuple: (source from .hgsub, revision from .hgsubstate, kind
       
    19     (key in types dict))
       
    20     """
       
    21     p = config.config()
       
    22     def read(f, sections=None, remap=None):
       
    23         if f in ctx:
       
    24             try:
       
    25                 data = ctx[f].data()
       
    26             except IOError, err:
       
    27                 if err.errno != errno.ENOENT:
       
    28                     raise
       
    29                 # handle missing subrepo spec files as removed
       
    30                 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
       
    31                 return
       
    32             p.parse(f, data, sections, remap, read)
       
    33         else:
       
    34             raise util.Abort(_("subrepo spec file %s not found") % f)
       
    35 
       
    36     if '.hgsub' in ctx:
       
    37         read('.hgsub')
       
    38 
       
    39     for path, src in ui.configitems('subpaths'):
       
    40         p.set('subpaths', path, src, ui.configsource('subpaths', path))
       
    41 
       
    42     rev = {}
       
    43     if '.hgsubstate' in ctx:
       
    44         try:
       
    45             for l in ctx['.hgsubstate'].data().splitlines():
       
    46                 revision, path = l.split(" ", 1)
       
    47                 rev[path] = revision
       
    48         except IOError, err:
       
    49             if err.errno != errno.ENOENT:
       
    50                 raise
       
    51 
       
    52     state = {}
       
    53     for path, src in p[''].items():
       
    54         kind = 'hg'
       
    55         if src.startswith('['):
       
    56             if ']' not in src:
       
    57                 raise util.Abort(_('missing ] in subrepo source'))
       
    58             kind, src = src.split(']', 1)
       
    59             kind = kind[1:]
       
    60 
       
    61         for pattern, repl in p.items('subpaths'):
       
    62             # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
       
    63             # does a string decode.
       
    64             repl = repl.encode('string-escape')
       
    65             # However, we still want to allow back references to go
       
    66             # through unharmed, so we turn r'\\1' into r'\1'. Again,
       
    67             # extra escapes are needed because re.sub string decodes.
       
    68             repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
       
    69             try:
       
    70                 src = re.sub(pattern, repl, src, 1)
       
    71             except re.error, e:
       
    72                 raise util.Abort(_("bad subrepository pattern in %s: %s")
       
    73                                  % (p.source('subpaths', pattern), e))
       
    74 
       
    75         state[path] = (src.strip(), rev.get(path, ''), kind)
       
    76 
       
    77     return state
       
    78 
       
    79 def writestate(repo, state):
       
    80     """rewrite .hgsubstate in (outer) repo with these subrepo states"""
       
    81     repo.wwrite('.hgsubstate',
       
    82                 ''.join(['%s %s\n' % (state[s][1], s)
       
    83                          for s in sorted(state)]), '')
       
    84 
       
    85 def submerge(repo, wctx, mctx, actx):
       
    86     """delegated from merge.applyupdates: merging of .hgsubstate file
       
    87     in working context, merging context and ancestor context"""
       
    88     if mctx == actx: # backwards?
       
    89         actx = wctx.p1()
       
    90     s1 = wctx.substate
       
    91     s2 = mctx.substate
       
    92     sa = actx.substate
       
    93     sm = {}
       
    94 
       
    95     repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
       
    96 
       
    97     def debug(s, msg, r=""):
       
    98         if r:
       
    99             r = "%s:%s:%s" % r
       
   100         repo.ui.debug("  subrepo %s: %s %s\n" % (s, msg, r))
       
   101 
       
   102     for s, l in s1.items():
       
   103         a = sa.get(s, nullstate)
       
   104         ld = l # local state with possible dirty flag for compares
       
   105         if wctx.sub(s).dirty():
       
   106             ld = (l[0], l[1] + "+")
       
   107         if wctx == actx: # overwrite
       
   108             a = ld
       
   109 
       
   110         if s in s2:
       
   111             r = s2[s]
       
   112             if ld == r or r == a: # no change or local is newer
       
   113                 sm[s] = l
       
   114                 continue
       
   115             elif ld == a: # other side changed
       
   116                 debug(s, "other changed, get", r)
       
   117                 wctx.sub(s).get(r)
       
   118                 sm[s] = r
       
   119             elif ld[0] != r[0]: # sources differ
       
   120                 if repo.ui.promptchoice(
       
   121                     _(' subrepository sources for %s differ\n'
       
   122                       'use (l)ocal source (%s) or (r)emote source (%s)?')
       
   123                       % (s, l[0], r[0]),
       
   124                       (_('&Local'), _('&Remote')), 0):
       
   125                     debug(s, "prompt changed, get", r)
       
   126                     wctx.sub(s).get(r)
       
   127                     sm[s] = r
       
   128             elif ld[1] == a[1]: # local side is unchanged
       
   129                 debug(s, "other side changed, get", r)
       
   130                 wctx.sub(s).get(r)
       
   131                 sm[s] = r
       
   132             else:
       
   133                 debug(s, "both sides changed, merge with", r)
       
   134                 wctx.sub(s).merge(r)
       
   135                 sm[s] = l
       
   136         elif ld == a: # remote removed, local unchanged
       
   137             debug(s, "remote removed, remove")
       
   138             wctx.sub(s).remove()
       
   139         else:
       
   140             if repo.ui.promptchoice(
       
   141                 _(' local changed subrepository %s which remote removed\n'
       
   142                   'use (c)hanged version or (d)elete?') % s,
       
   143                 (_('&Changed'), _('&Delete')), 0):
       
   144                 debug(s, "prompt remove")
       
   145                 wctx.sub(s).remove()
       
   146 
       
   147     for s, r in s2.items():
       
   148         if s in s1:
       
   149             continue
       
   150         elif s not in sa:
       
   151             debug(s, "remote added, get", r)
       
   152             mctx.sub(s).get(r)
       
   153             sm[s] = r
       
   154         elif r != sa[s]:
       
   155             if repo.ui.promptchoice(
       
   156                 _(' remote changed subrepository %s which local removed\n'
       
   157                   'use (c)hanged version or (d)elete?') % s,
       
   158                 (_('&Changed'), _('&Delete')), 0) == 0:
       
   159                 debug(s, "prompt recreate", r)
       
   160                 wctx.sub(s).get(r)
       
   161                 sm[s] = r
       
   162 
       
   163     # record merged .hgsubstate
       
   164     writestate(repo, sm)
       
   165 
       
   166 def reporelpath(repo):
       
   167     """return path to this (sub)repo as seen from outermost repo"""
       
   168     parent = repo
       
   169     while hasattr(parent, '_subparent'):
       
   170         parent = parent._subparent
       
   171     return repo.root[len(parent.root)+1:]
       
   172 
       
   173 def subrelpath(sub):
       
   174     """return path to this subrepo as seen from outermost repo"""
       
   175     if not hasattr(sub, '_repo'):
       
   176         return sub._path
       
   177     return reporelpath(sub._repo)
       
   178 
       
   179 def _abssource(repo, push=False, abort=True):
       
   180     """return pull/push path of repo - either based on parent repo .hgsub info
       
   181     or on the top repo config. Abort or return None if no source found."""
       
   182     if hasattr(repo, '_subparent'):
       
   183         source = repo._subsource
       
   184         if source.startswith('/') or '://' in source:
       
   185             return source
       
   186         parent = _abssource(repo._subparent, push, abort=False)
       
   187         if parent:
       
   188             if '://' in parent:
       
   189                 if parent[-1] == '/':
       
   190                     parent = parent[:-1]
       
   191                 r = urlparse.urlparse(parent + '/' + source)
       
   192                 r = urlparse.urlunparse((r[0], r[1],
       
   193                                          posixpath.normpath(r[2]),
       
   194                                          r[3], r[4], r[5]))
       
   195                 return r
       
   196             else: # plain file system path
       
   197                 return posixpath.normpath(os.path.join(parent, repo._subsource))
       
   198     else: # recursion reached top repo
       
   199         if hasattr(repo, '_subtoppath'):
       
   200             return repo._subtoppath
       
   201         if push and repo.ui.config('paths', 'default-push'):
       
   202             return repo.ui.config('paths', 'default-push')
       
   203         if repo.ui.config('paths', 'default'):
       
   204             return repo.ui.config('paths', 'default')
       
   205     if abort:
       
   206         raise util.Abort(_("default path for subrepository %s not found") %
       
   207             reporelpath(repo))
       
   208 
       
   209 def itersubrepos(ctx1, ctx2):
       
   210     """find subrepos in ctx1 or ctx2"""
       
   211     # Create a (subpath, ctx) mapping where we prefer subpaths from
       
   212     # ctx1. The subpaths from ctx2 are important when the .hgsub file
       
   213     # has been modified (in ctx2) but not yet committed (in ctx1).
       
   214     subpaths = dict.fromkeys(ctx2.substate, ctx2)
       
   215     subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
       
   216     for subpath, ctx in sorted(subpaths.iteritems()):
       
   217         yield subpath, ctx.sub(subpath)
       
   218 
       
   219 def subrepo(ctx, path):
       
   220     """return instance of the right subrepo class for subrepo in path"""
       
   221     # subrepo inherently violates our import layering rules
       
   222     # because it wants to make repo objects from deep inside the stack
       
   223     # so we manually delay the circular imports to not break
       
   224     # scripts that don't use our demand-loading
       
   225     global hg
       
   226     import hg as h
       
   227     hg = h
       
   228 
       
   229     util.path_auditor(ctx._repo.root)(path)
       
   230     state = ctx.substate.get(path, nullstate)
       
   231     if state[2] not in types:
       
   232         raise util.Abort(_('unknown subrepo type %s') % state[2])
       
   233     return types[state[2]](ctx, path, state[:2])
       
   234 
       
   235 # subrepo classes need to implement the following abstract class:
       
   236 
       
   237 class abstractsubrepo(object):
       
   238 
       
   239     def dirty(self):
       
   240         """returns true if the dirstate of the subrepo does not match
       
   241         current stored state
       
   242         """
       
   243         raise NotImplementedError
       
   244 
       
   245     def checknested(self, path):
       
   246         """check if path is a subrepository within this repository"""
       
   247         return False
       
   248 
       
   249     def commit(self, text, user, date):
       
   250         """commit the current changes to the subrepo with the given
       
   251         log message. Use given user and date if possible. Return the
       
   252         new state of the subrepo.
       
   253         """
       
   254         raise NotImplementedError
       
   255 
       
   256     def remove(self):
       
   257         """remove the subrepo
       
   258 
       
   259         (should verify the dirstate is not dirty first)
       
   260         """
       
   261         raise NotImplementedError
       
   262 
       
   263     def get(self, state):
       
   264         """run whatever commands are needed to put the subrepo into
       
   265         this state
       
   266         """
       
   267         raise NotImplementedError
       
   268 
       
   269     def merge(self, state):
       
   270         """merge currently-saved state with the new state."""
       
   271         raise NotImplementedError
       
   272 
       
   273     def push(self, force):
       
   274         """perform whatever action is analogous to 'hg push'
       
   275 
       
   276         This may be a no-op on some systems.
       
   277         """
       
   278         raise NotImplementedError
       
   279 
       
   280     def add(self, ui, match, dryrun, prefix):
       
   281         return []
       
   282 
       
   283     def status(self, rev2, **opts):
       
   284         return [], [], [], [], [], [], []
       
   285 
       
   286     def diff(self, diffopts, node2, match, prefix, **opts):
       
   287         pass
       
   288 
       
   289     def outgoing(self, ui, dest, opts):
       
   290         return 1
       
   291 
       
   292     def incoming(self, ui, source, opts):
       
   293         return 1
       
   294 
       
   295     def files(self):
       
   296         """return filename iterator"""
       
   297         raise NotImplementedError
       
   298 
       
   299     def filedata(self, name):
       
   300         """return file data"""
       
   301         raise NotImplementedError
       
   302 
       
   303     def fileflags(self, name):
       
   304         """return file flags"""
       
   305         return ''
       
   306 
       
   307     def archive(self, archiver, prefix):
       
   308         for name in self.files():
       
   309             flags = self.fileflags(name)
       
   310             mode = 'x' in flags and 0755 or 0644
       
   311             symlink = 'l' in flags
       
   312             archiver.addfile(os.path.join(prefix, self._path, name),
       
   313                              mode, symlink, self.filedata(name))
       
   314 
       
   315 
       
   316 class hgsubrepo(abstractsubrepo):
       
   317     def __init__(self, ctx, path, state):
       
   318         self._path = path
       
   319         self._state = state
       
   320         r = ctx._repo
       
   321         root = r.wjoin(path)
       
   322         create = False
       
   323         if not os.path.exists(os.path.join(root, '.hg')):
       
   324             create = True
       
   325             util.makedirs(root)
       
   326         self._repo = hg.repository(r.ui, root, create=create)
       
   327         self._repo._subparent = r
       
   328         self._repo._subsource = state[0]
       
   329 
       
   330         if create:
       
   331             fp = self._repo.opener("hgrc", "w", text=True)
       
   332             fp.write('[paths]\n')
       
   333 
       
   334             def addpathconfig(key, value):
       
   335                 if value:
       
   336                     fp.write('%s = %s\n' % (key, value))
       
   337                     self._repo.ui.setconfig('paths', key, value)
       
   338 
       
   339             defpath = _abssource(self._repo, abort=False)
       
   340             defpushpath = _abssource(self._repo, True, abort=False)
       
   341             addpathconfig('default', defpath)
       
   342             if defpath != defpushpath:
       
   343                 addpathconfig('default-push', defpushpath)
       
   344             fp.close()
       
   345 
       
   346     def add(self, ui, match, dryrun, prefix):
       
   347         return cmdutil.add(ui, self._repo, match, dryrun, True,
       
   348                            os.path.join(prefix, self._path))
       
   349 
       
   350     def status(self, rev2, **opts):
       
   351         try:
       
   352             rev1 = self._state[1]
       
   353             ctx1 = self._repo[rev1]
       
   354             ctx2 = self._repo[rev2]
       
   355             return self._repo.status(ctx1, ctx2, **opts)
       
   356         except error.RepoLookupError, inst:
       
   357             self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
       
   358                                % (inst, subrelpath(self)))
       
   359             return [], [], [], [], [], [], []
       
   360 
       
   361     def diff(self, diffopts, node2, match, prefix, **opts):
       
   362         try:
       
   363             node1 = node.bin(self._state[1])
       
   364             # We currently expect node2 to come from substate and be
       
   365             # in hex format
       
   366             if node2 is not None:
       
   367                 node2 = node.bin(node2)
       
   368             cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
       
   369                                    node1, node2, match,
       
   370                                    prefix=os.path.join(prefix, self._path),
       
   371                                    listsubrepos=True, **opts)
       
   372         except error.RepoLookupError, inst:
       
   373             self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
       
   374                                % (inst, subrelpath(self)))
       
   375 
       
   376     def archive(self, archiver, prefix):
       
   377         abstractsubrepo.archive(self, archiver, prefix)
       
   378 
       
   379         rev = self._state[1]
       
   380         ctx = self._repo[rev]
       
   381         for subpath in ctx.substate:
       
   382             s = subrepo(ctx, subpath)
       
   383             s.archive(archiver, os.path.join(prefix, self._path))
       
   384 
       
   385     def dirty(self):
       
   386         r = self._state[1]
       
   387         if r == '':
       
   388             return True
       
   389         w = self._repo[None]
       
   390         if w.p1() != self._repo[r]: # version checked out change
       
   391             return True
       
   392         return w.dirty() # working directory changed
       
   393 
       
   394     def checknested(self, path):
       
   395         return self._repo._checknested(self._repo.wjoin(path))
       
   396 
       
   397     def commit(self, text, user, date):
       
   398         self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
       
   399         n = self._repo.commit(text, user, date)
       
   400         if not n:
       
   401             return self._repo['.'].hex() # different version checked out
       
   402         return node.hex(n)
       
   403 
       
   404     def remove(self):
       
   405         # we can't fully delete the repository as it may contain
       
   406         # local-only history
       
   407         self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
       
   408         hg.clean(self._repo, node.nullid, False)
       
   409 
       
   410     def _get(self, state):
       
   411         source, revision, kind = state
       
   412         try:
       
   413             self._repo.lookup(revision)
       
   414         except error.RepoError:
       
   415             self._repo._subsource = source
       
   416             srcurl = _abssource(self._repo)
       
   417             self._repo.ui.status(_('pulling subrepo %s from %s\n')
       
   418                                  % (subrelpath(self), srcurl))
       
   419             other = hg.repository(self._repo.ui, srcurl)
       
   420             self._repo.pull(other)
       
   421 
       
   422     def get(self, state):
       
   423         self._get(state)
       
   424         source, revision, kind = state
       
   425         self._repo.ui.debug("getting subrepo %s\n" % self._path)
       
   426         hg.clean(self._repo, revision, False)
       
   427 
       
   428     def merge(self, state):
       
   429         self._get(state)
       
   430         cur = self._repo['.']
       
   431         dst = self._repo[state[1]]
       
   432         anc = dst.ancestor(cur)
       
   433         if anc == cur:
       
   434             self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
       
   435             hg.update(self._repo, state[1])
       
   436         elif anc == dst:
       
   437             self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
       
   438         else:
       
   439             self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
       
   440             hg.merge(self._repo, state[1], remind=False)
       
   441 
       
   442     def push(self, force):
       
   443         # push subrepos depth-first for coherent ordering
       
   444         c = self._repo['']
       
   445         subs = c.substate # only repos that are committed
       
   446         for s in sorted(subs):
       
   447             if not c.sub(s).push(force):
       
   448                 return False
       
   449 
       
   450         dsturl = _abssource(self._repo, True)
       
   451         self._repo.ui.status(_('pushing subrepo %s to %s\n') %
       
   452             (subrelpath(self), dsturl))
       
   453         other = hg.repository(self._repo.ui, dsturl)
       
   454         return self._repo.push(other, force)
       
   455 
       
   456     def outgoing(self, ui, dest, opts):
       
   457         return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
       
   458 
       
   459     def incoming(self, ui, source, opts):
       
   460         return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
       
   461 
       
   462     def files(self):
       
   463         rev = self._state[1]
       
   464         ctx = self._repo[rev]
       
   465         return ctx.manifest()
       
   466 
       
   467     def filedata(self, name):
       
   468         rev = self._state[1]
       
   469         return self._repo[rev][name].data()
       
   470 
       
   471     def fileflags(self, name):
       
   472         rev = self._state[1]
       
   473         ctx = self._repo[rev]
       
   474         return ctx.flags(name)
       
   475 
       
   476 
       
   477 class svnsubrepo(abstractsubrepo):
       
   478     def __init__(self, ctx, path, state):
       
   479         self._path = path
       
   480         self._state = state
       
   481         self._ctx = ctx
       
   482         self._ui = ctx._repo.ui
       
   483 
       
   484     def _svncommand(self, commands, filename=''):
       
   485         path = os.path.join(self._ctx._repo.origroot, self._path, filename)
       
   486         cmd = ['svn'] + commands + [path]
       
   487         cmd = [util.shellquote(arg) for arg in cmd]
       
   488         cmd = util.quotecommand(' '.join(cmd))
       
   489         env = dict(os.environ)
       
   490         # Avoid localized output, preserve current locale for everything else.
       
   491         env['LC_MESSAGES'] = 'C'
       
   492         p = subprocess.Popen(cmd, shell=True, bufsize=-1,
       
   493                              close_fds=util.closefds,
       
   494                              stdout=subprocess.PIPE, stderr=subprocess.PIPE,
       
   495                              universal_newlines=True, env=env)
       
   496         stdout, stderr = p.communicate()
       
   497         stderr = stderr.strip()
       
   498         if stderr:
       
   499             raise util.Abort(stderr)
       
   500         return stdout
       
   501 
       
   502     def _wcrev(self):
       
   503         output = self._svncommand(['info', '--xml'])
       
   504         doc = xml.dom.minidom.parseString(output)
       
   505         entries = doc.getElementsByTagName('entry')
       
   506         if not entries:
       
   507             return '0'
       
   508         return str(entries[0].getAttribute('revision')) or '0'
       
   509 
       
   510     def _wcchanged(self):
       
   511         """Return (changes, extchanges) where changes is True
       
   512         if the working directory was changed, and extchanges is
       
   513         True if any of these changes concern an external entry.
       
   514         """
       
   515         output = self._svncommand(['status', '--xml'])
       
   516         externals, changes = [], []
       
   517         doc = xml.dom.minidom.parseString(output)
       
   518         for e in doc.getElementsByTagName('entry'):
       
   519             s = e.getElementsByTagName('wc-status')
       
   520             if not s:
       
   521                 continue
       
   522             item = s[0].getAttribute('item')
       
   523             props = s[0].getAttribute('props')
       
   524             path = e.getAttribute('path')
       
   525             if item == 'external':
       
   526                 externals.append(path)
       
   527             if (item not in ('', 'normal', 'unversioned', 'external')
       
   528                 or props not in ('', 'none')):
       
   529                 changes.append(path)
       
   530         for path in changes:
       
   531             for ext in externals:
       
   532                 if path == ext or path.startswith(ext + os.sep):
       
   533                     return True, True
       
   534         return bool(changes), False
       
   535 
       
   536     def dirty(self):
       
   537         if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
       
   538             return False
       
   539         return True
       
   540 
       
   541     def commit(self, text, user, date):
       
   542         # user and date are out of our hands since svn is centralized
       
   543         changed, extchanged = self._wcchanged()
       
   544         if not changed:
       
   545             return self._wcrev()
       
   546         if extchanged:
       
   547             # Do not try to commit externals
       
   548             raise util.Abort(_('cannot commit svn externals'))
       
   549         commitinfo = self._svncommand(['commit', '-m', text])
       
   550         self._ui.status(commitinfo)
       
   551         newrev = re.search('Committed revision ([0-9]+).', commitinfo)
       
   552         if not newrev:
       
   553             raise util.Abort(commitinfo.splitlines()[-1])
       
   554         newrev = newrev.groups()[0]
       
   555         self._ui.status(self._svncommand(['update', '-r', newrev]))
       
   556         return newrev
       
   557 
       
   558     def remove(self):
       
   559         if self.dirty():
       
   560             self._ui.warn(_('not removing repo %s because '
       
   561                             'it has changes.\n' % self._path))
       
   562             return
       
   563         self._ui.note(_('removing subrepo %s\n') % self._path)
       
   564 
       
   565         def onerror(function, path, excinfo):
       
   566             if function is not os.remove:
       
   567                 raise
       
   568             # read-only files cannot be unlinked under Windows
       
   569             s = os.stat(path)
       
   570             if (s.st_mode & stat.S_IWRITE) != 0:
       
   571                 raise
       
   572             os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
       
   573             os.remove(path)
       
   574 
       
   575         path = self._ctx._repo.wjoin(self._path)
       
   576         shutil.rmtree(path, onerror=onerror)
       
   577         try:
       
   578             os.removedirs(os.path.dirname(path))
       
   579         except OSError:
       
   580             pass
       
   581 
       
   582     def get(self, state):
       
   583         status = self._svncommand(['checkout', state[0], '--revision', state[1]])
       
   584         if not re.search('Checked out revision [0-9]+.', status):
       
   585             raise util.Abort(status.splitlines()[-1])
       
   586         self._ui.status(status)
       
   587 
       
   588     def merge(self, state):
       
   589         old = int(self._state[1])
       
   590         new = int(state[1])
       
   591         if new > old:
       
   592             self.get(state)
       
   593 
       
   594     def push(self, force):
       
   595         # push is a no-op for SVN
       
   596         return True
       
   597 
       
   598     def files(self):
       
   599         output = self._svncommand(['list'])
       
   600         # This works because svn forbids \n in filenames.
       
   601         return output.splitlines()
       
   602 
       
   603     def filedata(self, name):
       
   604         return self._svncommand(['cat'], name)
       
   605 
       
   606 
       
   607 types = {
       
   608     'hg': hgsubrepo,
       
   609     'svn': svnsubrepo,
       
   610     }