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