Mercurial > hg
comparison mercurial/exchange.py @ 20345:8567b4ea76ac
exchange: extract push function from localrepo
The localrepo class if far too big. Push and pull logic will be extracted and
reworked to better fit with the fact they now exchange more than plain changeset
bundle.
This changeset extract the push code. later changeset will slowly slice this
over 200 hundred lines and 8 indentation level function into smaller saner
brick.
The localrepo.push method is kept for now to limit impact on user code. But it
will be ultimately removed, now that the public supposed API is hold by peer.
author | Pierre-Yves David <pierre-yves.david@logilab.fr> |
---|---|
date | Thu, 30 Jan 2014 15:34:01 -0800 |
parents | |
children | 42df1fe32552 |
comparison
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 |