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