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