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