eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/hg.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # hg.py - repository classes for mercurial
       
     2 #
       
     3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
       
     4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
       
     5 #
       
     6 # This software may be used and distributed according to the terms of the
       
     7 # GNU General Public License version 2 or any later version.
       
     8 
       
     9 from i18n import _
       
    10 from lock import release
       
    11 from node import hex, nullid, nullrev, short
       
    12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
       
    13 import lock, util, extensions, error, encoding, node
       
    14 import cmdutil, discovery, url
       
    15 import merge as mergemod
       
    16 import verify as verifymod
       
    17 import errno, os, shutil
       
    18 
       
    19 def _local(path):
       
    20     path = util.expandpath(util.drop_scheme('file', path))
       
    21     return (os.path.isfile(path) and bundlerepo or localrepo)
       
    22 
       
    23 def addbranchrevs(lrepo, repo, branches, revs):
       
    24     hashbranch, branches = branches
       
    25     if not hashbranch and not branches:
       
    26         return revs or None, revs and revs[0] or None
       
    27     revs = revs and list(revs) or []
       
    28     if not repo.capable('branchmap'):
       
    29         if branches:
       
    30             raise util.Abort(_("remote branch lookup not supported"))
       
    31         revs.append(hashbranch)
       
    32         return revs, revs[0]
       
    33     branchmap = repo.branchmap()
       
    34 
       
    35     def primary(butf8):
       
    36         if butf8 == '.':
       
    37             if not lrepo or not lrepo.local():
       
    38                 raise util.Abort(_("dirstate branch not accessible"))
       
    39             butf8 = lrepo.dirstate.branch()
       
    40         if butf8 in branchmap:
       
    41             revs.extend(node.hex(r) for r in reversed(branchmap[butf8]))
       
    42             return True
       
    43         else:
       
    44             return False
       
    45 
       
    46     for branch in branches:
       
    47         butf8 = encoding.fromlocal(branch)
       
    48         if not primary(butf8):
       
    49             raise error.RepoLookupError(_("unknown branch '%s'") % branch)
       
    50     if hashbranch:
       
    51         butf8 = encoding.fromlocal(hashbranch)
       
    52         if not primary(butf8):
       
    53             revs.append(hashbranch)
       
    54     return revs, revs[0]
       
    55 
       
    56 def parseurl(url, branches=None):
       
    57     '''parse url#branch, returning (url, (branch, branches))'''
       
    58 
       
    59     if '#' not in url:
       
    60         return url, (None, branches or [])
       
    61     url, branch = url.split('#', 1)
       
    62     return url, (branch, branches or [])
       
    63 
       
    64 schemes = {
       
    65     'bundle': bundlerepo,
       
    66     'file': _local,
       
    67     'http': httprepo,
       
    68     'https': httprepo,
       
    69     'ssh': sshrepo,
       
    70     'static-http': statichttprepo,
       
    71 }
       
    72 
       
    73 def _lookup(path):
       
    74     scheme = 'file'
       
    75     if path:
       
    76         c = path.find(':')
       
    77         if c > 0:
       
    78             scheme = path[:c]
       
    79     thing = schemes.get(scheme) or schemes['file']
       
    80     try:
       
    81         return thing(path)
       
    82     except TypeError:
       
    83         return thing
       
    84 
       
    85 def islocal(repo):
       
    86     '''return true if repo or path is local'''
       
    87     if isinstance(repo, str):
       
    88         try:
       
    89             return _lookup(repo).islocal(repo)
       
    90         except AttributeError:
       
    91             return False
       
    92     return repo.local()
       
    93 
       
    94 def repository(ui, path='', create=False):
       
    95     """return a repository object for the specified path"""
       
    96     repo = _lookup(path).instance(ui, path, create)
       
    97     ui = getattr(repo, "ui", ui)
       
    98     for name, module in extensions.extensions():
       
    99         hook = getattr(module, 'reposetup', None)
       
   100         if hook:
       
   101             hook(ui, repo)
       
   102     return repo
       
   103 
       
   104 def defaultdest(source):
       
   105     '''return default destination of clone if none is given'''
       
   106     return os.path.basename(os.path.normpath(source))
       
   107 
       
   108 def localpath(path):
       
   109     if path.startswith('file://localhost/'):
       
   110         return path[16:]
       
   111     if path.startswith('file://'):
       
   112         return path[7:]
       
   113     if path.startswith('file:'):
       
   114         return path[5:]
       
   115     return path
       
   116 
       
   117 def share(ui, source, dest=None, update=True):
       
   118     '''create a shared repository'''
       
   119 
       
   120     if not islocal(source):
       
   121         raise util.Abort(_('can only share local repositories'))
       
   122 
       
   123     if not dest:
       
   124         dest = defaultdest(source)
       
   125     else:
       
   126         dest = ui.expandpath(dest)
       
   127 
       
   128     if isinstance(source, str):
       
   129         origsource = ui.expandpath(source)
       
   130         source, branches = parseurl(origsource)
       
   131         srcrepo = repository(ui, source)
       
   132         rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
       
   133     else:
       
   134         srcrepo = source
       
   135         origsource = source = srcrepo.url()
       
   136         checkout = None
       
   137 
       
   138     sharedpath = srcrepo.sharedpath # if our source is already sharing
       
   139 
       
   140     root = os.path.realpath(dest)
       
   141     roothg = os.path.join(root, '.hg')
       
   142 
       
   143     if os.path.exists(roothg):
       
   144         raise util.Abort(_('destination already exists'))
       
   145 
       
   146     if not os.path.isdir(root):
       
   147         os.mkdir(root)
       
   148     os.mkdir(roothg)
       
   149 
       
   150     requirements = ''
       
   151     try:
       
   152         requirements = srcrepo.opener('requires').read()
       
   153     except IOError, inst:
       
   154         if inst.errno != errno.ENOENT:
       
   155             raise
       
   156 
       
   157     requirements += 'shared\n'
       
   158     file(os.path.join(roothg, 'requires'), 'w').write(requirements)
       
   159     file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
       
   160 
       
   161     default = srcrepo.ui.config('paths', 'default')
       
   162     if default:
       
   163         f = file(os.path.join(roothg, 'hgrc'), 'w')
       
   164         f.write('[paths]\ndefault = %s\n' % default)
       
   165         f.close()
       
   166 
       
   167     r = repository(ui, root)
       
   168 
       
   169     if update:
       
   170         r.ui.status(_("updating working directory\n"))
       
   171         if update is not True:
       
   172             checkout = update
       
   173         for test in (checkout, 'default', 'tip'):
       
   174             if test is None:
       
   175                 continue
       
   176             try:
       
   177                 uprev = r.lookup(test)
       
   178                 break
       
   179             except error.RepoLookupError:
       
   180                 continue
       
   181         _update(r, uprev)
       
   182 
       
   183 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
       
   184           stream=False, branch=None):
       
   185     """Make a copy of an existing repository.
       
   186 
       
   187     Create a copy of an existing repository in a new directory.  The
       
   188     source and destination are URLs, as passed to the repository
       
   189     function.  Returns a pair of repository objects, the source and
       
   190     newly created destination.
       
   191 
       
   192     The location of the source is added to the new repository's
       
   193     .hg/hgrc file, as the default to be used for future pulls and
       
   194     pushes.
       
   195 
       
   196     If an exception is raised, the partly cloned/updated destination
       
   197     repository will be deleted.
       
   198 
       
   199     Arguments:
       
   200 
       
   201     source: repository object or URL
       
   202 
       
   203     dest: URL of destination repository to create (defaults to base
       
   204     name of source repository)
       
   205 
       
   206     pull: always pull from source repository, even in local case
       
   207 
       
   208     stream: stream raw data uncompressed from repository (fast over
       
   209     LAN, slow over WAN)
       
   210 
       
   211     rev: revision to clone up to (implies pull=True)
       
   212 
       
   213     update: update working directory after clone completes, if
       
   214     destination is local repository (True means update to default rev,
       
   215     anything else is treated as a revision)
       
   216 
       
   217     branch: branches to clone
       
   218     """
       
   219 
       
   220     if isinstance(source, str):
       
   221         origsource = ui.expandpath(source)
       
   222         source, branch = parseurl(origsource, branch)
       
   223         src_repo = repository(ui, source)
       
   224     else:
       
   225         src_repo = source
       
   226         branch = (None, branch or [])
       
   227         origsource = source = src_repo.url()
       
   228     rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev)
       
   229 
       
   230     if dest is None:
       
   231         dest = defaultdest(source)
       
   232         ui.status(_("destination directory: %s\n") % dest)
       
   233     else:
       
   234         dest = ui.expandpath(dest)
       
   235 
       
   236     dest = localpath(dest)
       
   237     source = localpath(source)
       
   238 
       
   239     if os.path.exists(dest):
       
   240         if not os.path.isdir(dest):
       
   241             raise util.Abort(_("destination '%s' already exists") % dest)
       
   242         elif os.listdir(dest):
       
   243             raise util.Abort(_("destination '%s' is not empty") % dest)
       
   244 
       
   245     class DirCleanup(object):
       
   246         def __init__(self, dir_):
       
   247             self.rmtree = shutil.rmtree
       
   248             self.dir_ = dir_
       
   249         def close(self):
       
   250             self.dir_ = None
       
   251         def cleanup(self):
       
   252             if self.dir_:
       
   253                 self.rmtree(self.dir_, True)
       
   254 
       
   255     src_lock = dest_lock = dir_cleanup = None
       
   256     try:
       
   257         if islocal(dest):
       
   258             dir_cleanup = DirCleanup(dest)
       
   259 
       
   260         abspath = origsource
       
   261         copy = False
       
   262         if src_repo.cancopy() and islocal(dest):
       
   263             abspath = os.path.abspath(util.drop_scheme('file', origsource))
       
   264             copy = not pull and not rev
       
   265 
       
   266         if copy:
       
   267             try:
       
   268                 # we use a lock here because if we race with commit, we
       
   269                 # can end up with extra data in the cloned revlogs that's
       
   270                 # not pointed to by changesets, thus causing verify to
       
   271                 # fail
       
   272                 src_lock = src_repo.lock(wait=False)
       
   273             except error.LockError:
       
   274                 copy = False
       
   275 
       
   276         if copy:
       
   277             src_repo.hook('preoutgoing', throw=True, source='clone')
       
   278             hgdir = os.path.realpath(os.path.join(dest, ".hg"))
       
   279             if not os.path.exists(dest):
       
   280                 os.mkdir(dest)
       
   281             else:
       
   282                 # only clean up directories we create ourselves
       
   283                 dir_cleanup.dir_ = hgdir
       
   284             try:
       
   285                 dest_path = hgdir
       
   286                 os.mkdir(dest_path)
       
   287             except OSError, inst:
       
   288                 if inst.errno == errno.EEXIST:
       
   289                     dir_cleanup.close()
       
   290                     raise util.Abort(_("destination '%s' already exists")
       
   291                                      % dest)
       
   292                 raise
       
   293 
       
   294             hardlink = None
       
   295             num = 0
       
   296             for f in src_repo.store.copylist():
       
   297                 src = os.path.join(src_repo.sharedpath, f)
       
   298                 dst = os.path.join(dest_path, f)
       
   299                 dstbase = os.path.dirname(dst)
       
   300                 if dstbase and not os.path.exists(dstbase):
       
   301                     os.mkdir(dstbase)
       
   302                 if os.path.exists(src):
       
   303                     if dst.endswith('data'):
       
   304                         # lock to avoid premature writing to the target
       
   305                         dest_lock = lock.lock(os.path.join(dstbase, "lock"))
       
   306                     hardlink, n = util.copyfiles(src, dst, hardlink)
       
   307                     num += n
       
   308             if hardlink:
       
   309                 ui.debug("linked %d files\n" % num)
       
   310             else:
       
   311                 ui.debug("copied %d files\n" % num)
       
   312 
       
   313             # we need to re-init the repo after manually copying the data
       
   314             # into it
       
   315             dest_repo = repository(ui, dest)
       
   316             src_repo.hook('outgoing', source='clone',
       
   317                           node=node.hex(node.nullid))
       
   318         else:
       
   319             try:
       
   320                 dest_repo = repository(ui, dest, create=True)
       
   321             except OSError, inst:
       
   322                 if inst.errno == errno.EEXIST:
       
   323                     dir_cleanup.close()
       
   324                     raise util.Abort(_("destination '%s' already exists")
       
   325                                      % dest)
       
   326                 raise
       
   327 
       
   328             revs = None
       
   329             if rev:
       
   330                 if 'lookup' not in src_repo.capabilities:
       
   331                     raise util.Abort(_("src repository does not support "
       
   332                                        "revision lookup and so doesn't "
       
   333                                        "support clone by revision"))
       
   334                 revs = [src_repo.lookup(r) for r in rev]
       
   335                 checkout = revs[0]
       
   336             if dest_repo.local():
       
   337                 dest_repo.clone(src_repo, heads=revs, stream=stream)
       
   338             elif src_repo.local():
       
   339                 src_repo.push(dest_repo, revs=revs)
       
   340             else:
       
   341                 raise util.Abort(_("clone from remote to remote not supported"))
       
   342 
       
   343         if dir_cleanup:
       
   344             dir_cleanup.close()
       
   345 
       
   346         if dest_repo.local():
       
   347             fp = dest_repo.opener("hgrc", "w", text=True)
       
   348             fp.write("[paths]\n")
       
   349             fp.write("default = %s\n" % abspath)
       
   350             fp.close()
       
   351 
       
   352             dest_repo.ui.setconfig('paths', 'default', abspath)
       
   353 
       
   354             if update:
       
   355                 if update is not True:
       
   356                     checkout = update
       
   357                     if src_repo.local():
       
   358                         checkout = src_repo.lookup(update)
       
   359                 for test in (checkout, 'default', 'tip'):
       
   360                     if test is None:
       
   361                         continue
       
   362                     try:
       
   363                         uprev = dest_repo.lookup(test)
       
   364                         break
       
   365                     except error.RepoLookupError:
       
   366                         continue
       
   367                 bn = dest_repo[uprev].branch()
       
   368                 dest_repo.ui.status(_("updating to branch %s\n")
       
   369                                     % encoding.tolocal(bn))
       
   370                 _update(dest_repo, uprev)
       
   371 
       
   372         return src_repo, dest_repo
       
   373     finally:
       
   374         release(src_lock, dest_lock)
       
   375         if dir_cleanup is not None:
       
   376             dir_cleanup.cleanup()
       
   377 
       
   378 def _showstats(repo, stats):
       
   379     repo.ui.status(_("%d files updated, %d files merged, "
       
   380                      "%d files removed, %d files unresolved\n") % stats)
       
   381 
       
   382 def update(repo, node):
       
   383     """update the working directory to node, merging linear changes"""
       
   384     stats = mergemod.update(repo, node, False, False, None)
       
   385     _showstats(repo, stats)
       
   386     if stats[3]:
       
   387         repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
       
   388     return stats[3] > 0
       
   389 
       
   390 # naming conflict in clone()
       
   391 _update = update
       
   392 
       
   393 def clean(repo, node, show_stats=True):
       
   394     """forcibly switch the working directory to node, clobbering changes"""
       
   395     stats = mergemod.update(repo, node, False, True, None)
       
   396     if show_stats:
       
   397         _showstats(repo, stats)
       
   398     return stats[3] > 0
       
   399 
       
   400 def merge(repo, node, force=None, remind=True):
       
   401     """branch merge with node, resolving changes"""
       
   402     stats = mergemod.update(repo, node, True, force, False)
       
   403     _showstats(repo, stats)
       
   404     if stats[3]:
       
   405         repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
       
   406                          "or 'hg update -C .' to abandon\n"))
       
   407     elif remind:
       
   408         repo.ui.status(_("(branch merge, don't forget to commit)\n"))
       
   409     return stats[3] > 0
       
   410 
       
   411 def _incoming(displaychlist, subreporecurse, ui, repo, source,
       
   412         opts, buffered=False):
       
   413     """
       
   414     Helper for incoming / gincoming.
       
   415     displaychlist gets called with
       
   416         (remoterepo, incomingchangesetlist, displayer) parameters,
       
   417     and is supposed to contain only code that can't be unified.
       
   418     """
       
   419     source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
       
   420     other = repository(remoteui(repo, opts), source)
       
   421     ui.status(_('comparing with %s\n') % url.hidepassword(source))
       
   422     revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
       
   423 
       
   424     if revs:
       
   425         revs = [other.lookup(rev) for rev in revs]
       
   426     other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs,
       
   427                                 opts["bundle"], opts["force"])
       
   428     if incoming is None:
       
   429         ui.status(_("no changes found\n"))
       
   430         return subreporecurse()
       
   431 
       
   432     try:
       
   433         chlist = other.changelog.nodesbetween(incoming, revs)[0]
       
   434         displayer = cmdutil.show_changeset(ui, other, opts, buffered)
       
   435 
       
   436         # XXX once graphlog extension makes it into core,
       
   437         # should be replaced by a if graph/else
       
   438         displaychlist(other, chlist, displayer)
       
   439 
       
   440         displayer.close()
       
   441     finally:
       
   442         if hasattr(other, 'close'):
       
   443             other.close()
       
   444         if bundle:
       
   445             os.unlink(bundle)
       
   446     subreporecurse()
       
   447     return 0 # exit code is zero since we found incoming changes
       
   448 
       
   449 def incoming(ui, repo, source, opts):
       
   450     def subreporecurse():
       
   451         ret = 1
       
   452         if opts.get('subrepos'):
       
   453             ctx = repo[None]
       
   454             for subpath in sorted(ctx.substate):
       
   455                 sub = ctx.sub(subpath)
       
   456                 ret = min(ret, sub.incoming(ui, source, opts))
       
   457         return ret
       
   458 
       
   459     def display(other, chlist, displayer):
       
   460         limit = cmdutil.loglimit(opts)
       
   461         if opts.get('newest_first'):
       
   462             chlist.reverse()
       
   463         count = 0
       
   464         for n in chlist:
       
   465             if limit is not None and count >= limit:
       
   466                 break
       
   467             parents = [p for p in other.changelog.parents(n) if p != nullid]
       
   468             if opts.get('no_merges') and len(parents) == 2:
       
   469                 continue
       
   470             count += 1
       
   471             displayer.show(other[n])
       
   472     return _incoming(display, subreporecurse, ui, repo, source, opts)
       
   473 
       
   474 def _outgoing(ui, repo, dest, opts):
       
   475     dest = ui.expandpath(dest or 'default-push', dest or 'default')
       
   476     dest, branches = parseurl(dest, opts.get('branch'))
       
   477     revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
       
   478     if revs:
       
   479         revs = [repo.lookup(rev) for rev in revs]
       
   480 
       
   481     other = repository(remoteui(repo, opts), dest)
       
   482     ui.status(_('comparing with %s\n') % url.hidepassword(dest))
       
   483     o = discovery.findoutgoing(repo, other, force=opts.get('force'))
       
   484     if not o:
       
   485         ui.status(_("no changes found\n"))
       
   486         return None
       
   487 
       
   488     return repo.changelog.nodesbetween(o, revs)[0]
       
   489 
       
   490 def outgoing(ui, repo, dest, opts):
       
   491     def recurse():
       
   492         ret = 1
       
   493         if opts.get('subrepos'):
       
   494             ctx = repo[None]
       
   495             for subpath in sorted(ctx.substate):
       
   496                 sub = ctx.sub(subpath)
       
   497                 ret = min(ret, sub.outgoing(ui, dest, opts))
       
   498         return ret
       
   499 
       
   500     limit = cmdutil.loglimit(opts)
       
   501     o = _outgoing(ui, repo, dest, opts)
       
   502     if o is None:
       
   503         return recurse()
       
   504 
       
   505     if opts.get('newest_first'):
       
   506         o.reverse()
       
   507     displayer = cmdutil.show_changeset(ui, repo, opts)
       
   508     count = 0
       
   509     for n in o:
       
   510         if limit is not None and count >= limit:
       
   511             break
       
   512         parents = [p for p in repo.changelog.parents(n) if p != nullid]
       
   513         if opts.get('no_merges') and len(parents) == 2:
       
   514             continue
       
   515         count += 1
       
   516         displayer.show(repo[n])
       
   517     displayer.close()
       
   518     recurse()
       
   519     return 0 # exit code is zero since we found outgoing changes
       
   520 
       
   521 def revert(repo, node, choose):
       
   522     """revert changes to revision in node without updating dirstate"""
       
   523     return mergemod.update(repo, node, False, True, choose)[3] > 0
       
   524 
       
   525 def verify(repo):
       
   526     """verify the consistency of a repository"""
       
   527     return verifymod.verify(repo)
       
   528 
       
   529 def remoteui(src, opts):
       
   530     'build a remote ui from ui or repo and opts'
       
   531     if hasattr(src, 'baseui'): # looks like a repository
       
   532         dst = src.baseui.copy() # drop repo-specific config
       
   533         src = src.ui # copy target options from repo
       
   534     else: # assume it's a global ui object
       
   535         dst = src.copy() # keep all global options
       
   536 
       
   537     # copy ssh-specific options
       
   538     for o in 'ssh', 'remotecmd':
       
   539         v = opts.get(o) or src.config('ui', o)
       
   540         if v:
       
   541             dst.setconfig("ui", o, v)
       
   542 
       
   543     # copy bundle-specific options
       
   544     r = src.config('bundle', 'mainreporoot')
       
   545     if r:
       
   546         dst.setconfig('bundle', 'mainreporoot', r)
       
   547 
       
   548     # copy selected local settings to the remote ui
       
   549     for sect in ('auth', 'http_proxy'):
       
   550         for key, val in src.configitems(sect):
       
   551             dst.setconfig(sect, key, val)
       
   552     v = src.config('web', 'cacerts')
       
   553     if v:
       
   554         dst.setconfig('web', 'cacerts', v)
       
   555 
       
   556     return dst