mercurial/exchange.py
changeset 20345 8567b4ea76ac
child 20346 42df1fe32552
equal deleted inserted replaced
20344:b15ba2d95428 20345:8567b4ea76ac
       
     1 # exchange.py - utily to exchange data between repo.
       
     2 #
       
     3 # Copyright 2005-2007 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 from i18n import _
       
     9 from node import hex
       
    10 import errno
       
    11 import util, scmutil, changegroup
       
    12 import discovery, phases, obsolete, bookmarks
       
    13 
       
    14 def push(repo, remote, force=False, revs=None, newbranch=False):
       
    15     '''Push outgoing changesets (limited by revs) from a local
       
    16     repository to remote. Return an integer:
       
    17       - None means nothing to push
       
    18       - 0 means HTTP error
       
    19       - 1 means we pushed and remote head count is unchanged *or*
       
    20         we have outgoing changesets but refused to push
       
    21       - other values as described by addchangegroup()
       
    22     '''
       
    23     if remote.local():
       
    24         missing = set(repo.requirements) - remote.local().supported
       
    25         if missing:
       
    26             msg = _("required features are not"
       
    27                     " supported in the destination:"
       
    28                     " %s") % (', '.join(sorted(missing)))
       
    29             raise util.Abort(msg)
       
    30 
       
    31     # there are two ways to push to remote repo:
       
    32     #
       
    33     # addchangegroup assumes local user can lock remote
       
    34     # repo (local filesystem, old ssh servers).
       
    35     #
       
    36     # unbundle assumes local user cannot lock remote repo (new ssh
       
    37     # servers, http servers).
       
    38 
       
    39     if not remote.canpush():
       
    40         raise util.Abort(_("destination does not support push"))
       
    41     unfi = repo.unfiltered()
       
    42     def localphasemove(nodes, phase=phases.public):
       
    43         """move <nodes> to <phase> in the local source repo"""
       
    44         if locallock is not None:
       
    45             phases.advanceboundary(repo, phase, nodes)
       
    46         else:
       
    47             # repo is not locked, do not change any phases!
       
    48             # Informs the user that phases should have been moved when
       
    49             # applicable.
       
    50             actualmoves = [n for n in nodes if phase < repo[n].phase()]
       
    51             phasestr = phases.phasenames[phase]
       
    52             if actualmoves:
       
    53                 repo.ui.status(_('cannot lock source repo, skipping local'
       
    54                                  ' %s phase update\n') % phasestr)
       
    55     # get local lock as we might write phase data
       
    56     locallock = None
       
    57     try:
       
    58         locallock = repo.lock()
       
    59     except IOError, err:
       
    60         if err.errno != errno.EACCES:
       
    61             raise
       
    62         # source repo cannot be locked.
       
    63         # We do not abort the push, but just disable the local phase
       
    64         # synchronisation.
       
    65         msg = 'cannot lock source repository: %s\n' % err
       
    66         repo.ui.debug(msg)
       
    67     try:
       
    68         repo.checkpush(force, revs)
       
    69         lock = None
       
    70         unbundle = remote.capable('unbundle')
       
    71         if not unbundle:
       
    72             lock = remote.lock()
       
    73         try:
       
    74             # discovery
       
    75             fci = discovery.findcommonincoming
       
    76             commoninc = fci(unfi, remote, force=force)
       
    77             common, inc, remoteheads = commoninc
       
    78             fco = discovery.findcommonoutgoing
       
    79             outgoing = fco(unfi, remote, onlyheads=revs,
       
    80                            commoninc=commoninc, force=force)
       
    81 
       
    82 
       
    83             if not outgoing.missing:
       
    84                 # nothing to push
       
    85                 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
       
    86                 ret = None
       
    87             else:
       
    88                 # something to push
       
    89                 if not force:
       
    90                     # if repo.obsstore == False --> no obsolete
       
    91                     # then, save the iteration
       
    92                     if unfi.obsstore:
       
    93                         # this message are here for 80 char limit reason
       
    94                         mso = _("push includes obsolete changeset: %s!")
       
    95                         mst = "push includes %s changeset: %s!"
       
    96                         # plain versions for i18n tool to detect them
       
    97                         _("push includes unstable changeset: %s!")
       
    98                         _("push includes bumped changeset: %s!")
       
    99                         _("push includes divergent changeset: %s!")
       
   100                         # If we are to push if there is at least one
       
   101                         # obsolete or unstable changeset in missing, at
       
   102                         # least one of the missinghead will be obsolete or
       
   103                         # unstable. So checking heads only is ok
       
   104                         for node in outgoing.missingheads:
       
   105                             ctx = unfi[node]
       
   106                             if ctx.obsolete():
       
   107                                 raise util.Abort(mso % ctx)
       
   108                             elif ctx.troubled():
       
   109                                 raise util.Abort(_(mst)
       
   110                                                  % (ctx.troubles()[0],
       
   111                                                     ctx))
       
   112                     newbm = repo.ui.configlist('bookmarks', 'pushing')
       
   113                     discovery.checkheads(unfi, remote, outgoing,
       
   114                                          remoteheads, newbranch,
       
   115                                          bool(inc), newbm)
       
   116 
       
   117                 # TODO: get bundlecaps from remote
       
   118                 bundlecaps = None
       
   119                 # create a changegroup from local
       
   120                 if revs is None and not (outgoing.excluded
       
   121                                          or repo.changelog.filteredrevs):
       
   122                     # push everything,
       
   123                     # use the fast path, no race possible on push
       
   124                     bundler = changegroup.bundle10(repo, bundlecaps)
       
   125                     cg = repo._changegroupsubset(outgoing,
       
   126                                                  bundler,
       
   127                                                  'push',
       
   128                                                  fastpath=True)
       
   129                 else:
       
   130                     cg = repo.getlocalbundle('push', outgoing, bundlecaps)
       
   131 
       
   132                 # apply changegroup to remote
       
   133                 if unbundle:
       
   134                     # local repo finds heads on server, finds out what
       
   135                     # revs it must push. once revs transferred, if server
       
   136                     # finds it has different heads (someone else won
       
   137                     # commit/push race), server aborts.
       
   138                     if force:
       
   139                         remoteheads = ['force']
       
   140                     # ssh: return remote's addchangegroup()
       
   141                     # http: return remote's addchangegroup() or 0 for error
       
   142                     ret = remote.unbundle(cg, remoteheads, 'push')
       
   143                 else:
       
   144                     # we return an integer indicating remote head count
       
   145                     # change
       
   146                     ret = remote.addchangegroup(cg, 'push', repo.url())
       
   147 
       
   148             if ret:
       
   149                 # push succeed, synchronize target of the push
       
   150                 cheads = outgoing.missingheads
       
   151             elif revs is None:
       
   152                 # All out push fails. synchronize all common
       
   153                 cheads = outgoing.commonheads
       
   154             else:
       
   155                 # I want cheads = heads(::missingheads and ::commonheads)
       
   156                 # (missingheads is revs with secret changeset filtered out)
       
   157                 #
       
   158                 # This can be expressed as:
       
   159                 #     cheads = ( (missingheads and ::commonheads)
       
   160                 #              + (commonheads and ::missingheads))"
       
   161                 #              )
       
   162                 #
       
   163                 # while trying to push we already computed the following:
       
   164                 #     common = (::commonheads)
       
   165                 #     missing = ((commonheads::missingheads) - commonheads)
       
   166                 #
       
   167                 # We can pick:
       
   168                 # * missingheads part of common (::commonheads)
       
   169                 common = set(outgoing.common)
       
   170                 nm = repo.changelog.nodemap
       
   171                 cheads = [node for node in revs if nm[node] in common]
       
   172                 # and
       
   173                 # * commonheads parents on missing
       
   174                 revset = unfi.set('%ln and parents(roots(%ln))',
       
   175                                  outgoing.commonheads,
       
   176                                  outgoing.missing)
       
   177                 cheads.extend(c.node() for c in revset)
       
   178             # even when we don't push, exchanging phase data is useful
       
   179             remotephases = remote.listkeys('phases')
       
   180             if (repo.ui.configbool('ui', '_usedassubrepo', False)
       
   181                 and remotephases    # server supports phases
       
   182                 and ret is None # nothing was pushed
       
   183                 and remotephases.get('publishing', False)):
       
   184                 # When:
       
   185                 # - this is a subrepo push
       
   186                 # - and remote support phase
       
   187                 # - and no changeset was pushed
       
   188                 # - and remote is publishing
       
   189                 # We may be in issue 3871 case!
       
   190                 # We drop the possible phase synchronisation done by
       
   191                 # courtesy to publish changesets possibly locally draft
       
   192                 # on the remote.
       
   193                 remotephases = {'publishing': 'True'}
       
   194             if not remotephases: # old server or public only repo
       
   195                 localphasemove(cheads)
       
   196                 # don't push any phase data as there is nothing to push
       
   197             else:
       
   198                 ana = phases.analyzeremotephases(repo, cheads, remotephases)
       
   199                 pheads, droots = ana
       
   200                 ### Apply remote phase on local
       
   201                 if remotephases.get('publishing', False):
       
   202                     localphasemove(cheads)
       
   203                 else: # publish = False
       
   204                     localphasemove(pheads)
       
   205                     localphasemove(cheads, phases.draft)
       
   206                 ### Apply local phase on remote
       
   207 
       
   208                 # Get the list of all revs draft on remote by public here.
       
   209                 # XXX Beware that revset break if droots is not strictly
       
   210                 # XXX root we may want to ensure it is but it is costly
       
   211                 outdated =  unfi.set('heads((%ln::%ln) and public())',
       
   212                                      droots, cheads)
       
   213                 for newremotehead in outdated:
       
   214                     r = remote.pushkey('phases',
       
   215                                        newremotehead.hex(),
       
   216                                        str(phases.draft),
       
   217                                        str(phases.public))
       
   218                     if not r:
       
   219                         repo.ui.warn(_('updating %s to public failed!\n')
       
   220                                         % newremotehead)
       
   221             repo.ui.debug('try to push obsolete markers to remote\n')
       
   222             obsolete.syncpush(repo, remote)
       
   223         finally:
       
   224             if lock is not None:
       
   225                 lock.release()
       
   226     finally:
       
   227         if locallock is not None:
       
   228             locallock.release()
       
   229 
       
   230     bookmarks.updateremote(repo.ui, unfi, remote, revs)
       
   231     return ret