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