##// END OF EJS Templates
push: move discovery in its own function...
Pierre-Yves David -
r20466:233623d5 default
parent child Browse files
Show More
@@ -1,333 +1,335 b''
1 1 # exchange.py - utily to exchange data between repo.
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 from node import hex
10 10 import errno
11 11 import util, scmutil, changegroup
12 12 import discovery, phases, obsolete, bookmarks
13 13
14 14
15 15 class pushoperation(object):
16 16 """A object that represent a single push operation
17 17
18 18 It purpose is to carry push related state and very common operation.
19 19
20 20 A new should be created at the begining of each push and discarded
21 21 afterward.
22 22 """
23 23
24 24 def __init__(self, repo, remote, force=False, revs=None, newbranch=False):
25 25 # repo we push from
26 26 self.repo = repo
27 27 self.ui = repo.ui
28 28 # repo we push to
29 29 self.remote = remote
30 30 # force option provided
31 31 self.force = force
32 32 # revs to be pushed (None is "all")
33 33 self.revs = revs
34 34 # allow push of new branch
35 35 self.newbranch = newbranch
36 36 # did a local lock get acquired?
37 37 self.locallocked = None
38 38 # Integer version of the push result
39 39 # - None means nothing to push
40 40 # - 0 means HTTP error
41 41 # - 1 means we pushed and remote head count is unchanged *or*
42 42 # we have outgoing changesets but refused to push
43 43 # - other values as described by addchangegroup()
44 44 self.ret = None
45 45 # discover.outgoing object (contains common and outgoin data)
46 46 self.outgoing = None
47 47 # all remote heads before the push
48 48 self.remoteheads = None
49 49 # testable as a boolean indicating if any nodes are missing locally.
50 50 self.incoming = None
51 51
52 52 def push(repo, remote, force=False, revs=None, newbranch=False):
53 53 '''Push outgoing changesets (limited by revs) from a local
54 54 repository to remote. Return an integer:
55 55 - None means nothing to push
56 56 - 0 means HTTP error
57 57 - 1 means we pushed and remote head count is unchanged *or*
58 58 we have outgoing changesets but refused to push
59 59 - other values as described by addchangegroup()
60 60 '''
61 61 pushop = pushoperation(repo, remote, force, revs, newbranch)
62 62 if pushop.remote.local():
63 63 missing = (set(pushop.repo.requirements)
64 64 - pushop.remote.local().supported)
65 65 if missing:
66 66 msg = _("required features are not"
67 67 " supported in the destination:"
68 68 " %s") % (', '.join(sorted(missing)))
69 69 raise util.Abort(msg)
70 70
71 71 # there are two ways to push to remote repo:
72 72 #
73 73 # addchangegroup assumes local user can lock remote
74 74 # repo (local filesystem, old ssh servers).
75 75 #
76 76 # unbundle assumes local user cannot lock remote repo (new ssh
77 77 # servers, http servers).
78 78
79 79 if not pushop.remote.canpush():
80 80 raise util.Abort(_("destination does not support push"))
81 unfi = pushop.repo.unfiltered()
82 81 # get local lock as we might write phase data
83 82 locallock = None
84 83 try:
85 84 locallock = pushop.repo.lock()
86 85 pushop.locallocked = True
87 86 except IOError, err:
88 87 pushop.locallocked = False
89 88 if err.errno != errno.EACCES:
90 89 raise
91 90 # source repo cannot be locked.
92 91 # We do not abort the push, but just disable the local phase
93 92 # synchronisation.
94 93 msg = 'cannot lock source repository: %s\n' % err
95 94 pushop.ui.debug(msg)
96 95 try:
97 96 pushop.repo.checkpush(pushop.force, pushop.revs)
98 97 lock = None
99 98 unbundle = pushop.remote.capable('unbundle')
100 99 if not unbundle:
101 100 lock = pushop.remote.lock()
102 101 try:
103 # discovery
104 fci = discovery.findcommonincoming
105 commoninc = fci(unfi, pushop.remote, force=pushop.force)
106 common, inc, remoteheads = commoninc
107 fco = discovery.findcommonoutgoing
108 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
109 commoninc=commoninc, force=pushop.force)
110 pushop.outgoing = outgoing
111 pushop.remoteheads = remoteheads
112 pushop.incoming = inc
113
102 _pushdiscovery(pushop)
114 103 if _pushcheckoutgoing(pushop):
115 104 _pushchangeset(pushop)
116 105 _pushsyncphase(pushop)
117 106 _pushobsolete(pushop)
118 107 finally:
119 108 if lock is not None:
120 109 lock.release()
121 110 finally:
122 111 if locallock is not None:
123 112 locallock.release()
124 113
125 114 _pushbookmark(pushop)
126 115 return pushop.ret
127 116
117 def _pushdiscovery(pushop):
118 # discovery
119 unfi = pushop.repo.unfiltered()
120 fci = discovery.findcommonincoming
121 commoninc = fci(unfi, pushop.remote, force=pushop.force)
122 common, inc, remoteheads = commoninc
123 fco = discovery.findcommonoutgoing
124 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
125 commoninc=commoninc, force=pushop.force)
126 pushop.outgoing = outgoing
127 pushop.remoteheads = remoteheads
128 pushop.incoming = inc
129
128 130 def _pushcheckoutgoing(pushop):
129 131 outgoing = pushop.outgoing
130 132 unfi = pushop.repo.unfiltered()
131 133 if not outgoing.missing:
132 134 # nothing to push
133 135 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
134 136 return False
135 137 # something to push
136 138 if not pushop.force:
137 139 # if repo.obsstore == False --> no obsolete
138 140 # then, save the iteration
139 141 if unfi.obsstore:
140 142 # this message are here for 80 char limit reason
141 143 mso = _("push includes obsolete changeset: %s!")
142 144 mst = "push includes %s changeset: %s!"
143 145 # plain versions for i18n tool to detect them
144 146 _("push includes unstable changeset: %s!")
145 147 _("push includes bumped changeset: %s!")
146 148 _("push includes divergent changeset: %s!")
147 149 # If we are to push if there is at least one
148 150 # obsolete or unstable changeset in missing, at
149 151 # least one of the missinghead will be obsolete or
150 152 # unstable. So checking heads only is ok
151 153 for node in outgoing.missingheads:
152 154 ctx = unfi[node]
153 155 if ctx.obsolete():
154 156 raise util.Abort(mso % ctx)
155 157 elif ctx.troubled():
156 158 raise util.Abort(_(mst)
157 159 % (ctx.troubles()[0],
158 160 ctx))
159 161 newbm = pushop.ui.configlist('bookmarks', 'pushing')
160 162 discovery.checkheads(unfi, pushop.remote, outgoing,
161 163 pushop.remoteheads,
162 164 pushop.newbranch,
163 165 bool(pushop.incoming),
164 166 newbm)
165 167 return True
166 168
167 169 def _pushchangeset(pushop):
168 170 """Make the actual push of changeset bundle to remote repo"""
169 171 outgoing = pushop.outgoing
170 172 unbundle = pushop.remote.capable('unbundle')
171 173 # TODO: get bundlecaps from remote
172 174 bundlecaps = None
173 175 # create a changegroup from local
174 176 if pushop.revs is None and not (outgoing.excluded
175 177 or pushop.repo.changelog.filteredrevs):
176 178 # push everything,
177 179 # use the fast path, no race possible on push
178 180 bundler = changegroup.bundle10(pushop.repo, bundlecaps)
179 181 cg = pushop.repo._changegroupsubset(outgoing,
180 182 bundler,
181 183 'push',
182 184 fastpath=True)
183 185 else:
184 186 cg = pushop.repo.getlocalbundle('push', outgoing, bundlecaps)
185 187
186 188 # apply changegroup to remote
187 189 if unbundle:
188 190 # local repo finds heads on server, finds out what
189 191 # revs it must push. once revs transferred, if server
190 192 # finds it has different heads (someone else won
191 193 # commit/push race), server aborts.
192 194 if pushop.force:
193 195 remoteheads = ['force']
194 196 else:
195 197 remoteheads = pushop.remoteheads
196 198 # ssh: return remote's addchangegroup()
197 199 # http: return remote's addchangegroup() or 0 for error
198 200 pushop.ret = pushop.remote.unbundle(cg, remoteheads,
199 201 'push')
200 202 else:
201 203 # we return an integer indicating remote head count
202 204 # change
203 205 pushop.ret = pushop.remote.addchangegroup(cg, 'push',
204 206 pushop.repo.url())
205 207
206 208 def _pushsyncphase(pushop):
207 209 """synchronise phase information locally and remotly"""
208 210 unfi = pushop.repo.unfiltered()
209 211 if pushop.ret:
210 212 # push succeed, synchronize target of the push
211 213 cheads = pushop.outgoing.missingheads
212 214 elif pushop.revs is None:
213 215 # All out push fails. synchronize all common
214 216 cheads = pushop.outgoing.commonheads
215 217 else:
216 218 # I want cheads = heads(::missingheads and ::commonheads)
217 219 # (missingheads is revs with secret changeset filtered out)
218 220 #
219 221 # This can be expressed as:
220 222 # cheads = ( (missingheads and ::commonheads)
221 223 # + (commonheads and ::missingheads))"
222 224 # )
223 225 #
224 226 # while trying to push we already computed the following:
225 227 # common = (::commonheads)
226 228 # missing = ((commonheads::missingheads) - commonheads)
227 229 #
228 230 # We can pick:
229 231 # * missingheads part of common (::commonheads)
230 232 common = set(pushop.outgoing.common)
231 233 nm = pushop.repo.changelog.nodemap
232 234 cheads = [node for node in pushop.revs if nm[node] in common]
233 235 # and
234 236 # * commonheads parents on missing
235 237 revset = unfi.set('%ln and parents(roots(%ln))',
236 238 pushop.outgoing.commonheads,
237 239 pushop.outgoing.missing)
238 240 cheads.extend(c.node() for c in revset)
239 241 # even when we don't push, exchanging phase data is useful
240 242 remotephases = pushop.remote.listkeys('phases')
241 243 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
242 244 and remotephases # server supports phases
243 245 and pushop.ret is None # nothing was pushed
244 246 and remotephases.get('publishing', False)):
245 247 # When:
246 248 # - this is a subrepo push
247 249 # - and remote support phase
248 250 # - and no changeset was pushed
249 251 # - and remote is publishing
250 252 # We may be in issue 3871 case!
251 253 # We drop the possible phase synchronisation done by
252 254 # courtesy to publish changesets possibly locally draft
253 255 # on the remote.
254 256 remotephases = {'publishing': 'True'}
255 257 if not remotephases: # old server or public only rer
256 258 _localphasemove(pushop, cheads)
257 259 # don't push any phase data as there is nothing to push
258 260 else:
259 261 ana = phases.analyzeremotephases(pushop.repo, cheads,
260 262 remotephases)
261 263 pheads, droots = ana
262 264 ### Apply remote phase on local
263 265 if remotephases.get('publishing', False):
264 266 _localphasemove(pushop, cheads)
265 267 else: # publish = False
266 268 _localphasemove(pushop, pheads)
267 269 _localphasemove(pushop, cheads, phases.draft)
268 270 ### Apply local phase on remote
269 271
270 272 # Get the list of all revs draft on remote by public here.
271 273 # XXX Beware that revset break if droots is not strictly
272 274 # XXX root we may want to ensure it is but it is costly
273 275 outdated = unfi.set('heads((%ln::%ln) and public())',
274 276 droots, cheads)
275 277 for newremotehead in outdated:
276 278 r = pushop.remote.pushkey('phases',
277 279 newremotehead.hex(),
278 280 str(phases.draft),
279 281 str(phases.public))
280 282 if not r:
281 283 pushop.ui.warn(_('updating %s to public failed!\n')
282 284 % newremotehead)
283 285
284 286 def _localphasemove(pushop, nodes, phase=phases.public):
285 287 """move <nodes> to <phase> in the local source repo"""
286 288 if pushop.locallocked:
287 289 phases.advanceboundary(pushop.repo, phase, nodes)
288 290 else:
289 291 # repo is not locked, do not change any phases!
290 292 # Informs the user that phases should have been moved when
291 293 # applicable.
292 294 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
293 295 phasestr = phases.phasenames[phase]
294 296 if actualmoves:
295 297 pushop.ui.status(_('cannot lock source repo, skipping '
296 298 'local %s phase update\n') % phasestr)
297 299
298 300 def _pushobsolete(pushop):
299 301 """utility function to push obsolete markers to a remote"""
300 302 pushop.ui.debug('try to push obsolete markers to remote\n')
301 303 repo = pushop.repo
302 304 remote = pushop.remote
303 305 if (obsolete._enabled and repo.obsstore and
304 306 'obsolete' in remote.listkeys('namespaces')):
305 307 rslts = []
306 308 remotedata = repo.listkeys('obsolete')
307 309 for key in sorted(remotedata, reverse=True):
308 310 # reverse sort to ensure we end with dump0
309 311 data = remotedata[key]
310 312 rslts.append(remote.pushkey('obsolete', key, '', data))
311 313 if [r for r in rslts if not r]:
312 314 msg = _('failed to push some obsolete markers!\n')
313 315 repo.ui.warn(msg)
314 316
315 317 def _pushbookmark(pushop):
316 318 """Update bookmark position on remote"""
317 319 ui = pushop.ui
318 320 repo = pushop.repo.unfiltered()
319 321 remote = pushop.remote
320 322 ui.debug("checking for updated bookmarks\n")
321 323 revnums = map(repo.changelog.rev, pushop.revs or [])
322 324 ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
323 325 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
324 326 ) = bookmarks.compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
325 327 srchex=hex)
326 328
327 329 for b, scid, dcid in advsrc:
328 330 if ancestors and repo[scid].rev() not in ancestors:
329 331 continue
330 332 if remote.pushkey('bookmarks', b, dcid, scid):
331 333 ui.status(_("updating bookmark %s\n") % b)
332 334 else:
333 335 ui.warn(_('updating bookmark %s failed!\n') % b)
General Comments 0
You need to be logged in to leave comments. Login now