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