##// END OF EJS Templates
push: send highest changegroup format supported by both side...
Pierre-Yves David -
r23180:116b80d6 default
parent child Browse files
Show More
@@ -1,1286 +1,1300 b''
1 # exchange.py - utility to exchange data between repos.
1 # exchange.py - utility to exchange data between repos.
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, nullid
9 from node import hex, nullid
10 import errno, urllib
10 import errno, urllib
11 import util, scmutil, changegroup, base85, error
11 import util, scmutil, changegroup, base85, error
12 import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey
12 import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey
13
13
14 def readbundle(ui, fh, fname, vfs=None):
14 def readbundle(ui, fh, fname, vfs=None):
15 header = changegroup.readexactly(fh, 4)
15 header = changegroup.readexactly(fh, 4)
16
16
17 alg = None
17 alg = None
18 if not fname:
18 if not fname:
19 fname = "stream"
19 fname = "stream"
20 if not header.startswith('HG') and header.startswith('\0'):
20 if not header.startswith('HG') and header.startswith('\0'):
21 fh = changegroup.headerlessfixup(fh, header)
21 fh = changegroup.headerlessfixup(fh, header)
22 header = "HG10"
22 header = "HG10"
23 alg = 'UN'
23 alg = 'UN'
24 elif vfs:
24 elif vfs:
25 fname = vfs.join(fname)
25 fname = vfs.join(fname)
26
26
27 magic, version = header[0:2], header[2:4]
27 magic, version = header[0:2], header[2:4]
28
28
29 if magic != 'HG':
29 if magic != 'HG':
30 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
30 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
31 if version == '10':
31 if version == '10':
32 if alg is None:
32 if alg is None:
33 alg = changegroup.readexactly(fh, 2)
33 alg = changegroup.readexactly(fh, 2)
34 return changegroup.cg1unpacker(fh, alg)
34 return changegroup.cg1unpacker(fh, alg)
35 elif version == '2Y':
35 elif version == '2Y':
36 return bundle2.unbundle20(ui, fh, header=magic + version)
36 return bundle2.unbundle20(ui, fh, header=magic + version)
37 else:
37 else:
38 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
38 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
39
39
40 def buildobsmarkerspart(bundler, markers):
40 def buildobsmarkerspart(bundler, markers):
41 """add an obsmarker part to the bundler with <markers>
41 """add an obsmarker part to the bundler with <markers>
42
42
43 No part is created if markers is empty.
43 No part is created if markers is empty.
44 Raises ValueError if the bundler doesn't support any known obsmarker format.
44 Raises ValueError if the bundler doesn't support any known obsmarker format.
45 """
45 """
46 if markers:
46 if markers:
47 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
47 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
48 version = obsolete.commonversion(remoteversions)
48 version = obsolete.commonversion(remoteversions)
49 if version is None:
49 if version is None:
50 raise ValueError('bundler do not support common obsmarker format')
50 raise ValueError('bundler do not support common obsmarker format')
51 stream = obsolete.encodemarkers(markers, True, version=version)
51 stream = obsolete.encodemarkers(markers, True, version=version)
52 return bundler.newpart('B2X:OBSMARKERS', data=stream)
52 return bundler.newpart('B2X:OBSMARKERS', data=stream)
53 return None
53 return None
54
54
55 class pushoperation(object):
55 class pushoperation(object):
56 """A object that represent a single push operation
56 """A object that represent a single push operation
57
57
58 It purpose is to carry push related state and very common operation.
58 It purpose is to carry push related state and very common operation.
59
59
60 A new should be created at the beginning of each push and discarded
60 A new should be created at the beginning of each push and discarded
61 afterward.
61 afterward.
62 """
62 """
63
63
64 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
64 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
65 bookmarks=()):
65 bookmarks=()):
66 # repo we push from
66 # repo we push from
67 self.repo = repo
67 self.repo = repo
68 self.ui = repo.ui
68 self.ui = repo.ui
69 # repo we push to
69 # repo we push to
70 self.remote = remote
70 self.remote = remote
71 # force option provided
71 # force option provided
72 self.force = force
72 self.force = force
73 # revs to be pushed (None is "all")
73 # revs to be pushed (None is "all")
74 self.revs = revs
74 self.revs = revs
75 # bookmark explicitly pushed
75 # bookmark explicitly pushed
76 self.bookmarks = bookmarks
76 self.bookmarks = bookmarks
77 # allow push of new branch
77 # allow push of new branch
78 self.newbranch = newbranch
78 self.newbranch = newbranch
79 # did a local lock get acquired?
79 # did a local lock get acquired?
80 self.locallocked = None
80 self.locallocked = None
81 # step already performed
81 # step already performed
82 # (used to check what steps have been already performed through bundle2)
82 # (used to check what steps have been already performed through bundle2)
83 self.stepsdone = set()
83 self.stepsdone = set()
84 # Integer version of the changegroup push result
84 # Integer version of the changegroup push result
85 # - None means nothing to push
85 # - None means nothing to push
86 # - 0 means HTTP error
86 # - 0 means HTTP error
87 # - 1 means we pushed and remote head count is unchanged *or*
87 # - 1 means we pushed and remote head count is unchanged *or*
88 # we have outgoing changesets but refused to push
88 # we have outgoing changesets but refused to push
89 # - other values as described by addchangegroup()
89 # - other values as described by addchangegroup()
90 self.cgresult = None
90 self.cgresult = None
91 # Boolean value for the bookmark push
91 # Boolean value for the bookmark push
92 self.bkresult = None
92 self.bkresult = None
93 # discover.outgoing object (contains common and outgoing data)
93 # discover.outgoing object (contains common and outgoing data)
94 self.outgoing = None
94 self.outgoing = None
95 # all remote heads before the push
95 # all remote heads before the push
96 self.remoteheads = None
96 self.remoteheads = None
97 # testable as a boolean indicating if any nodes are missing locally.
97 # testable as a boolean indicating if any nodes are missing locally.
98 self.incoming = None
98 self.incoming = None
99 # phases changes that must be pushed along side the changesets
99 # phases changes that must be pushed along side the changesets
100 self.outdatedphases = None
100 self.outdatedphases = None
101 # phases changes that must be pushed if changeset push fails
101 # phases changes that must be pushed if changeset push fails
102 self.fallbackoutdatedphases = None
102 self.fallbackoutdatedphases = None
103 # outgoing obsmarkers
103 # outgoing obsmarkers
104 self.outobsmarkers = set()
104 self.outobsmarkers = set()
105 # outgoing bookmarks
105 # outgoing bookmarks
106 self.outbookmarks = []
106 self.outbookmarks = []
107
107
108 @util.propertycache
108 @util.propertycache
109 def futureheads(self):
109 def futureheads(self):
110 """future remote heads if the changeset push succeeds"""
110 """future remote heads if the changeset push succeeds"""
111 return self.outgoing.missingheads
111 return self.outgoing.missingheads
112
112
113 @util.propertycache
113 @util.propertycache
114 def fallbackheads(self):
114 def fallbackheads(self):
115 """future remote heads if the changeset push fails"""
115 """future remote heads if the changeset push fails"""
116 if self.revs is None:
116 if self.revs is None:
117 # not target to push, all common are relevant
117 # not target to push, all common are relevant
118 return self.outgoing.commonheads
118 return self.outgoing.commonheads
119 unfi = self.repo.unfiltered()
119 unfi = self.repo.unfiltered()
120 # I want cheads = heads(::missingheads and ::commonheads)
120 # I want cheads = heads(::missingheads and ::commonheads)
121 # (missingheads is revs with secret changeset filtered out)
121 # (missingheads is revs with secret changeset filtered out)
122 #
122 #
123 # This can be expressed as:
123 # This can be expressed as:
124 # cheads = ( (missingheads and ::commonheads)
124 # cheads = ( (missingheads and ::commonheads)
125 # + (commonheads and ::missingheads))"
125 # + (commonheads and ::missingheads))"
126 # )
126 # )
127 #
127 #
128 # while trying to push we already computed the following:
128 # while trying to push we already computed the following:
129 # common = (::commonheads)
129 # common = (::commonheads)
130 # missing = ((commonheads::missingheads) - commonheads)
130 # missing = ((commonheads::missingheads) - commonheads)
131 #
131 #
132 # We can pick:
132 # We can pick:
133 # * missingheads part of common (::commonheads)
133 # * missingheads part of common (::commonheads)
134 common = set(self.outgoing.common)
134 common = set(self.outgoing.common)
135 nm = self.repo.changelog.nodemap
135 nm = self.repo.changelog.nodemap
136 cheads = [node for node in self.revs if nm[node] in common]
136 cheads = [node for node in self.revs if nm[node] in common]
137 # and
137 # and
138 # * commonheads parents on missing
138 # * commonheads parents on missing
139 revset = unfi.set('%ln and parents(roots(%ln))',
139 revset = unfi.set('%ln and parents(roots(%ln))',
140 self.outgoing.commonheads,
140 self.outgoing.commonheads,
141 self.outgoing.missing)
141 self.outgoing.missing)
142 cheads.extend(c.node() for c in revset)
142 cheads.extend(c.node() for c in revset)
143 return cheads
143 return cheads
144
144
145 @property
145 @property
146 def commonheads(self):
146 def commonheads(self):
147 """set of all common heads after changeset bundle push"""
147 """set of all common heads after changeset bundle push"""
148 if self.cgresult:
148 if self.cgresult:
149 return self.futureheads
149 return self.futureheads
150 else:
150 else:
151 return self.fallbackheads
151 return self.fallbackheads
152
152
153 # mapping of message used when pushing bookmark
153 # mapping of message used when pushing bookmark
154 bookmsgmap = {'update': (_("updating bookmark %s\n"),
154 bookmsgmap = {'update': (_("updating bookmark %s\n"),
155 _('updating bookmark %s failed!\n')),
155 _('updating bookmark %s failed!\n')),
156 'export': (_("exporting bookmark %s\n"),
156 'export': (_("exporting bookmark %s\n"),
157 _('exporting bookmark %s failed!\n')),
157 _('exporting bookmark %s failed!\n')),
158 'delete': (_("deleting remote bookmark %s\n"),
158 'delete': (_("deleting remote bookmark %s\n"),
159 _('deleting remote bookmark %s failed!\n')),
159 _('deleting remote bookmark %s failed!\n')),
160 }
160 }
161
161
162
162
163 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=()):
163 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=()):
164 '''Push outgoing changesets (limited by revs) from a local
164 '''Push outgoing changesets (limited by revs) from a local
165 repository to remote. Return an integer:
165 repository to remote. Return an integer:
166 - None means nothing to push
166 - None means nothing to push
167 - 0 means HTTP error
167 - 0 means HTTP error
168 - 1 means we pushed and remote head count is unchanged *or*
168 - 1 means we pushed and remote head count is unchanged *or*
169 we have outgoing changesets but refused to push
169 we have outgoing changesets but refused to push
170 - other values as described by addchangegroup()
170 - other values as described by addchangegroup()
171 '''
171 '''
172 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks)
172 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks)
173 if pushop.remote.local():
173 if pushop.remote.local():
174 missing = (set(pushop.repo.requirements)
174 missing = (set(pushop.repo.requirements)
175 - pushop.remote.local().supported)
175 - pushop.remote.local().supported)
176 if missing:
176 if missing:
177 msg = _("required features are not"
177 msg = _("required features are not"
178 " supported in the destination:"
178 " supported in the destination:"
179 " %s") % (', '.join(sorted(missing)))
179 " %s") % (', '.join(sorted(missing)))
180 raise util.Abort(msg)
180 raise util.Abort(msg)
181
181
182 # there are two ways to push to remote repo:
182 # there are two ways to push to remote repo:
183 #
183 #
184 # addchangegroup assumes local user can lock remote
184 # addchangegroup assumes local user can lock remote
185 # repo (local filesystem, old ssh servers).
185 # repo (local filesystem, old ssh servers).
186 #
186 #
187 # unbundle assumes local user cannot lock remote repo (new ssh
187 # unbundle assumes local user cannot lock remote repo (new ssh
188 # servers, http servers).
188 # servers, http servers).
189
189
190 if not pushop.remote.canpush():
190 if not pushop.remote.canpush():
191 raise util.Abort(_("destination does not support push"))
191 raise util.Abort(_("destination does not support push"))
192 # get local lock as we might write phase data
192 # get local lock as we might write phase data
193 locallock = None
193 locallock = None
194 try:
194 try:
195 locallock = pushop.repo.lock()
195 locallock = pushop.repo.lock()
196 pushop.locallocked = True
196 pushop.locallocked = True
197 except IOError, err:
197 except IOError, err:
198 pushop.locallocked = False
198 pushop.locallocked = False
199 if err.errno != errno.EACCES:
199 if err.errno != errno.EACCES:
200 raise
200 raise
201 # source repo cannot be locked.
201 # source repo cannot be locked.
202 # We do not abort the push, but just disable the local phase
202 # We do not abort the push, but just disable the local phase
203 # synchronisation.
203 # synchronisation.
204 msg = 'cannot lock source repository: %s\n' % err
204 msg = 'cannot lock source repository: %s\n' % err
205 pushop.ui.debug(msg)
205 pushop.ui.debug(msg)
206 try:
206 try:
207 pushop.repo.checkpush(pushop)
207 pushop.repo.checkpush(pushop)
208 lock = None
208 lock = None
209 unbundle = pushop.remote.capable('unbundle')
209 unbundle = pushop.remote.capable('unbundle')
210 if not unbundle:
210 if not unbundle:
211 lock = pushop.remote.lock()
211 lock = pushop.remote.lock()
212 try:
212 try:
213 _pushdiscovery(pushop)
213 _pushdiscovery(pushop)
214 if (pushop.repo.ui.configbool('experimental', 'bundle2-exp',
214 if (pushop.repo.ui.configbool('experimental', 'bundle2-exp',
215 False)
215 False)
216 and pushop.remote.capable('bundle2-exp')):
216 and pushop.remote.capable('bundle2-exp')):
217 _pushbundle2(pushop)
217 _pushbundle2(pushop)
218 _pushchangeset(pushop)
218 _pushchangeset(pushop)
219 _pushsyncphase(pushop)
219 _pushsyncphase(pushop)
220 _pushobsolete(pushop)
220 _pushobsolete(pushop)
221 _pushbookmark(pushop)
221 _pushbookmark(pushop)
222 finally:
222 finally:
223 if lock is not None:
223 if lock is not None:
224 lock.release()
224 lock.release()
225 finally:
225 finally:
226 if locallock is not None:
226 if locallock is not None:
227 locallock.release()
227 locallock.release()
228
228
229 return pushop
229 return pushop
230
230
231 # list of steps to perform discovery before push
231 # list of steps to perform discovery before push
232 pushdiscoveryorder = []
232 pushdiscoveryorder = []
233
233
234 # Mapping between step name and function
234 # Mapping between step name and function
235 #
235 #
236 # This exists to help extensions wrap steps if necessary
236 # This exists to help extensions wrap steps if necessary
237 pushdiscoverymapping = {}
237 pushdiscoverymapping = {}
238
238
239 def pushdiscovery(stepname):
239 def pushdiscovery(stepname):
240 """decorator for function performing discovery before push
240 """decorator for function performing discovery before push
241
241
242 The function is added to the step -> function mapping and appended to the
242 The function is added to the step -> function mapping and appended to the
243 list of steps. Beware that decorated function will be added in order (this
243 list of steps. Beware that decorated function will be added in order (this
244 may matter).
244 may matter).
245
245
246 You can only use this decorator for a new step, if you want to wrap a step
246 You can only use this decorator for a new step, if you want to wrap a step
247 from an extension, change the pushdiscovery dictionary directly."""
247 from an extension, change the pushdiscovery dictionary directly."""
248 def dec(func):
248 def dec(func):
249 assert stepname not in pushdiscoverymapping
249 assert stepname not in pushdiscoverymapping
250 pushdiscoverymapping[stepname] = func
250 pushdiscoverymapping[stepname] = func
251 pushdiscoveryorder.append(stepname)
251 pushdiscoveryorder.append(stepname)
252 return func
252 return func
253 return dec
253 return dec
254
254
255 def _pushdiscovery(pushop):
255 def _pushdiscovery(pushop):
256 """Run all discovery steps"""
256 """Run all discovery steps"""
257 for stepname in pushdiscoveryorder:
257 for stepname in pushdiscoveryorder:
258 step = pushdiscoverymapping[stepname]
258 step = pushdiscoverymapping[stepname]
259 step(pushop)
259 step(pushop)
260
260
261 @pushdiscovery('changeset')
261 @pushdiscovery('changeset')
262 def _pushdiscoverychangeset(pushop):
262 def _pushdiscoverychangeset(pushop):
263 """discover the changeset that need to be pushed"""
263 """discover the changeset that need to be pushed"""
264 unfi = pushop.repo.unfiltered()
264 unfi = pushop.repo.unfiltered()
265 fci = discovery.findcommonincoming
265 fci = discovery.findcommonincoming
266 commoninc = fci(unfi, pushop.remote, force=pushop.force)
266 commoninc = fci(unfi, pushop.remote, force=pushop.force)
267 common, inc, remoteheads = commoninc
267 common, inc, remoteheads = commoninc
268 fco = discovery.findcommonoutgoing
268 fco = discovery.findcommonoutgoing
269 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
269 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
270 commoninc=commoninc, force=pushop.force)
270 commoninc=commoninc, force=pushop.force)
271 pushop.outgoing = outgoing
271 pushop.outgoing = outgoing
272 pushop.remoteheads = remoteheads
272 pushop.remoteheads = remoteheads
273 pushop.incoming = inc
273 pushop.incoming = inc
274
274
275 @pushdiscovery('phase')
275 @pushdiscovery('phase')
276 def _pushdiscoveryphase(pushop):
276 def _pushdiscoveryphase(pushop):
277 """discover the phase that needs to be pushed
277 """discover the phase that needs to be pushed
278
278
279 (computed for both success and failure case for changesets push)"""
279 (computed for both success and failure case for changesets push)"""
280 outgoing = pushop.outgoing
280 outgoing = pushop.outgoing
281 unfi = pushop.repo.unfiltered()
281 unfi = pushop.repo.unfiltered()
282 remotephases = pushop.remote.listkeys('phases')
282 remotephases = pushop.remote.listkeys('phases')
283 publishing = remotephases.get('publishing', False)
283 publishing = remotephases.get('publishing', False)
284 ana = phases.analyzeremotephases(pushop.repo,
284 ana = phases.analyzeremotephases(pushop.repo,
285 pushop.fallbackheads,
285 pushop.fallbackheads,
286 remotephases)
286 remotephases)
287 pheads, droots = ana
287 pheads, droots = ana
288 extracond = ''
288 extracond = ''
289 if not publishing:
289 if not publishing:
290 extracond = ' and public()'
290 extracond = ' and public()'
291 revset = 'heads((%%ln::%%ln) %s)' % extracond
291 revset = 'heads((%%ln::%%ln) %s)' % extracond
292 # Get the list of all revs draft on remote by public here.
292 # Get the list of all revs draft on remote by public here.
293 # XXX Beware that revset break if droots is not strictly
293 # XXX Beware that revset break if droots is not strictly
294 # XXX root we may want to ensure it is but it is costly
294 # XXX root we may want to ensure it is but it is costly
295 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
295 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
296 if not outgoing.missing:
296 if not outgoing.missing:
297 future = fallback
297 future = fallback
298 else:
298 else:
299 # adds changeset we are going to push as draft
299 # adds changeset we are going to push as draft
300 #
300 #
301 # should not be necessary for publishing server, but because of an
301 # should not be necessary for publishing server, but because of an
302 # issue fixed in xxxxx we have to do it anyway.
302 # issue fixed in xxxxx we have to do it anyway.
303 fdroots = list(unfi.set('roots(%ln + %ln::)',
303 fdroots = list(unfi.set('roots(%ln + %ln::)',
304 outgoing.missing, droots))
304 outgoing.missing, droots))
305 fdroots = [f.node() for f in fdroots]
305 fdroots = [f.node() for f in fdroots]
306 future = list(unfi.set(revset, fdroots, pushop.futureheads))
306 future = list(unfi.set(revset, fdroots, pushop.futureheads))
307 pushop.outdatedphases = future
307 pushop.outdatedphases = future
308 pushop.fallbackoutdatedphases = fallback
308 pushop.fallbackoutdatedphases = fallback
309
309
310 @pushdiscovery('obsmarker')
310 @pushdiscovery('obsmarker')
311 def _pushdiscoveryobsmarkers(pushop):
311 def _pushdiscoveryobsmarkers(pushop):
312 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
312 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
313 and pushop.repo.obsstore
313 and pushop.repo.obsstore
314 and 'obsolete' in pushop.remote.listkeys('namespaces')):
314 and 'obsolete' in pushop.remote.listkeys('namespaces')):
315 repo = pushop.repo
315 repo = pushop.repo
316 # very naive computation, that can be quite expensive on big repo.
316 # very naive computation, that can be quite expensive on big repo.
317 # However: evolution is currently slow on them anyway.
317 # However: evolution is currently slow on them anyway.
318 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
318 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
319 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
319 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
320
320
321 @pushdiscovery('bookmarks')
321 @pushdiscovery('bookmarks')
322 def _pushdiscoverybookmarks(pushop):
322 def _pushdiscoverybookmarks(pushop):
323 ui = pushop.ui
323 ui = pushop.ui
324 repo = pushop.repo.unfiltered()
324 repo = pushop.repo.unfiltered()
325 remote = pushop.remote
325 remote = pushop.remote
326 ui.debug("checking for updated bookmarks\n")
326 ui.debug("checking for updated bookmarks\n")
327 ancestors = ()
327 ancestors = ()
328 if pushop.revs:
328 if pushop.revs:
329 revnums = map(repo.changelog.rev, pushop.revs)
329 revnums = map(repo.changelog.rev, pushop.revs)
330 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
330 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
331 remotebookmark = remote.listkeys('bookmarks')
331 remotebookmark = remote.listkeys('bookmarks')
332
332
333 explicit = set(pushop.bookmarks)
333 explicit = set(pushop.bookmarks)
334
334
335 comp = bookmod.compare(repo, repo._bookmarks, remotebookmark, srchex=hex)
335 comp = bookmod.compare(repo, repo._bookmarks, remotebookmark, srchex=hex)
336 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
336 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
337 for b, scid, dcid in advsrc:
337 for b, scid, dcid in advsrc:
338 if b in explicit:
338 if b in explicit:
339 explicit.remove(b)
339 explicit.remove(b)
340 if not ancestors or repo[scid].rev() in ancestors:
340 if not ancestors or repo[scid].rev() in ancestors:
341 pushop.outbookmarks.append((b, dcid, scid))
341 pushop.outbookmarks.append((b, dcid, scid))
342 # search added bookmark
342 # search added bookmark
343 for b, scid, dcid in addsrc:
343 for b, scid, dcid in addsrc:
344 if b in explicit:
344 if b in explicit:
345 explicit.remove(b)
345 explicit.remove(b)
346 pushop.outbookmarks.append((b, '', scid))
346 pushop.outbookmarks.append((b, '', scid))
347 # search for overwritten bookmark
347 # search for overwritten bookmark
348 for b, scid, dcid in advdst + diverge + differ:
348 for b, scid, dcid in advdst + diverge + differ:
349 if b in explicit:
349 if b in explicit:
350 explicit.remove(b)
350 explicit.remove(b)
351 pushop.outbookmarks.append((b, dcid, scid))
351 pushop.outbookmarks.append((b, dcid, scid))
352 # search for bookmark to delete
352 # search for bookmark to delete
353 for b, scid, dcid in adddst:
353 for b, scid, dcid in adddst:
354 if b in explicit:
354 if b in explicit:
355 explicit.remove(b)
355 explicit.remove(b)
356 # treat as "deleted locally"
356 # treat as "deleted locally"
357 pushop.outbookmarks.append((b, dcid, ''))
357 pushop.outbookmarks.append((b, dcid, ''))
358 # identical bookmarks shouldn't get reported
358 # identical bookmarks shouldn't get reported
359 for b, scid, dcid in same:
359 for b, scid, dcid in same:
360 if b in explicit:
360 if b in explicit:
361 explicit.remove(b)
361 explicit.remove(b)
362
362
363 if explicit:
363 if explicit:
364 explicit = sorted(explicit)
364 explicit = sorted(explicit)
365 # we should probably list all of them
365 # we should probably list all of them
366 ui.warn(_('bookmark %s does not exist on the local '
366 ui.warn(_('bookmark %s does not exist on the local '
367 'or remote repository!\n') % explicit[0])
367 'or remote repository!\n') % explicit[0])
368 pushop.bkresult = 2
368 pushop.bkresult = 2
369
369
370 pushop.outbookmarks.sort()
370 pushop.outbookmarks.sort()
371
371
372 def _pushcheckoutgoing(pushop):
372 def _pushcheckoutgoing(pushop):
373 outgoing = pushop.outgoing
373 outgoing = pushop.outgoing
374 unfi = pushop.repo.unfiltered()
374 unfi = pushop.repo.unfiltered()
375 if not outgoing.missing:
375 if not outgoing.missing:
376 # nothing to push
376 # nothing to push
377 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
377 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
378 return False
378 return False
379 # something to push
379 # something to push
380 if not pushop.force:
380 if not pushop.force:
381 # if repo.obsstore == False --> no obsolete
381 # if repo.obsstore == False --> no obsolete
382 # then, save the iteration
382 # then, save the iteration
383 if unfi.obsstore:
383 if unfi.obsstore:
384 # this message are here for 80 char limit reason
384 # this message are here for 80 char limit reason
385 mso = _("push includes obsolete changeset: %s!")
385 mso = _("push includes obsolete changeset: %s!")
386 mst = {"unstable": _("push includes unstable changeset: %s!"),
386 mst = {"unstable": _("push includes unstable changeset: %s!"),
387 "bumped": _("push includes bumped changeset: %s!"),
387 "bumped": _("push includes bumped changeset: %s!"),
388 "divergent": _("push includes divergent changeset: %s!")}
388 "divergent": _("push includes divergent changeset: %s!")}
389 # If we are to push if there is at least one
389 # If we are to push if there is at least one
390 # obsolete or unstable changeset in missing, at
390 # obsolete or unstable changeset in missing, at
391 # least one of the missinghead will be obsolete or
391 # least one of the missinghead will be obsolete or
392 # unstable. So checking heads only is ok
392 # unstable. So checking heads only is ok
393 for node in outgoing.missingheads:
393 for node in outgoing.missingheads:
394 ctx = unfi[node]
394 ctx = unfi[node]
395 if ctx.obsolete():
395 if ctx.obsolete():
396 raise util.Abort(mso % ctx)
396 raise util.Abort(mso % ctx)
397 elif ctx.troubled():
397 elif ctx.troubled():
398 raise util.Abort(mst[ctx.troubles()[0]] % ctx)
398 raise util.Abort(mst[ctx.troubles()[0]] % ctx)
399 newbm = pushop.ui.configlist('bookmarks', 'pushing')
399 newbm = pushop.ui.configlist('bookmarks', 'pushing')
400 discovery.checkheads(unfi, pushop.remote, outgoing,
400 discovery.checkheads(unfi, pushop.remote, outgoing,
401 pushop.remoteheads,
401 pushop.remoteheads,
402 pushop.newbranch,
402 pushop.newbranch,
403 bool(pushop.incoming),
403 bool(pushop.incoming),
404 newbm)
404 newbm)
405 return True
405 return True
406
406
407 # List of names of steps to perform for an outgoing bundle2, order matters.
407 # List of names of steps to perform for an outgoing bundle2, order matters.
408 b2partsgenorder = []
408 b2partsgenorder = []
409
409
410 # Mapping between step name and function
410 # Mapping between step name and function
411 #
411 #
412 # This exists to help extensions wrap steps if necessary
412 # This exists to help extensions wrap steps if necessary
413 b2partsgenmapping = {}
413 b2partsgenmapping = {}
414
414
415 def b2partsgenerator(stepname):
415 def b2partsgenerator(stepname):
416 """decorator for function generating bundle2 part
416 """decorator for function generating bundle2 part
417
417
418 The function is added to the step -> function mapping and appended to the
418 The function is added to the step -> function mapping and appended to the
419 list of steps. Beware that decorated functions will be added in order
419 list of steps. Beware that decorated functions will be added in order
420 (this may matter).
420 (this may matter).
421
421
422 You can only use this decorator for new steps, if you want to wrap a step
422 You can only use this decorator for new steps, if you want to wrap a step
423 from an extension, attack the b2partsgenmapping dictionary directly."""
423 from an extension, attack the b2partsgenmapping dictionary directly."""
424 def dec(func):
424 def dec(func):
425 assert stepname not in b2partsgenmapping
425 assert stepname not in b2partsgenmapping
426 b2partsgenmapping[stepname] = func
426 b2partsgenmapping[stepname] = func
427 b2partsgenorder.append(stepname)
427 b2partsgenorder.append(stepname)
428 return func
428 return func
429 return dec
429 return dec
430
430
431 @b2partsgenerator('changeset')
431 @b2partsgenerator('changeset')
432 def _pushb2ctx(pushop, bundler):
432 def _pushb2ctx(pushop, bundler):
433 """handle changegroup push through bundle2
433 """handle changegroup push through bundle2
434
434
435 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
435 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
436 """
436 """
437 if 'changesets' in pushop.stepsdone:
437 if 'changesets' in pushop.stepsdone:
438 return
438 return
439 pushop.stepsdone.add('changesets')
439 pushop.stepsdone.add('changesets')
440 # Send known heads to the server for race detection.
440 # Send known heads to the server for race detection.
441 if not _pushcheckoutgoing(pushop):
441 if not _pushcheckoutgoing(pushop):
442 return
442 return
443 pushop.repo.prepushoutgoinghooks(pushop.repo,
443 pushop.repo.prepushoutgoinghooks(pushop.repo,
444 pushop.remote,
444 pushop.remote,
445 pushop.outgoing)
445 pushop.outgoing)
446 if not pushop.force:
446 if not pushop.force:
447 bundler.newpart('B2X:CHECK:HEADS', data=iter(pushop.remoteheads))
447 bundler.newpart('B2X:CHECK:HEADS', data=iter(pushop.remoteheads))
448 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
448 b2caps = bundle2.bundle2caps(pushop.remote)
449 pushop.outgoing)
449 version = None
450 cgversions = b2caps.get('b2x:changegroup')
451 if cgversions is None:
452 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
453 pushop.outgoing)
454 else:
455 cgversions = [v for v in cgversions if v in changegroup.packermap]
456 if not cgversions:
457 raise ValueError(_('no common changegroup version'))
458 version = max(cgversions)
459 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
460 pushop.outgoing,
461 version=version)
450 cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg)
462 cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg)
463 if version is not None:
464 cgpart.addparam('version', version)
451 def handlereply(op):
465 def handlereply(op):
452 """extract addchangegroup returns from server reply"""
466 """extract addchangegroup returns from server reply"""
453 cgreplies = op.records.getreplies(cgpart.id)
467 cgreplies = op.records.getreplies(cgpart.id)
454 assert len(cgreplies['changegroup']) == 1
468 assert len(cgreplies['changegroup']) == 1
455 pushop.cgresult = cgreplies['changegroup'][0]['return']
469 pushop.cgresult = cgreplies['changegroup'][0]['return']
456 return handlereply
470 return handlereply
457
471
458 @b2partsgenerator('phase')
472 @b2partsgenerator('phase')
459 def _pushb2phases(pushop, bundler):
473 def _pushb2phases(pushop, bundler):
460 """handle phase push through bundle2"""
474 """handle phase push through bundle2"""
461 if 'phases' in pushop.stepsdone:
475 if 'phases' in pushop.stepsdone:
462 return
476 return
463 b2caps = bundle2.bundle2caps(pushop.remote)
477 b2caps = bundle2.bundle2caps(pushop.remote)
464 if not 'b2x:pushkey' in b2caps:
478 if not 'b2x:pushkey' in b2caps:
465 return
479 return
466 pushop.stepsdone.add('phases')
480 pushop.stepsdone.add('phases')
467 part2node = []
481 part2node = []
468 enc = pushkey.encode
482 enc = pushkey.encode
469 for newremotehead in pushop.outdatedphases:
483 for newremotehead in pushop.outdatedphases:
470 part = bundler.newpart('b2x:pushkey')
484 part = bundler.newpart('b2x:pushkey')
471 part.addparam('namespace', enc('phases'))
485 part.addparam('namespace', enc('phases'))
472 part.addparam('key', enc(newremotehead.hex()))
486 part.addparam('key', enc(newremotehead.hex()))
473 part.addparam('old', enc(str(phases.draft)))
487 part.addparam('old', enc(str(phases.draft)))
474 part.addparam('new', enc(str(phases.public)))
488 part.addparam('new', enc(str(phases.public)))
475 part2node.append((part.id, newremotehead))
489 part2node.append((part.id, newremotehead))
476 def handlereply(op):
490 def handlereply(op):
477 for partid, node in part2node:
491 for partid, node in part2node:
478 partrep = op.records.getreplies(partid)
492 partrep = op.records.getreplies(partid)
479 results = partrep['pushkey']
493 results = partrep['pushkey']
480 assert len(results) <= 1
494 assert len(results) <= 1
481 msg = None
495 msg = None
482 if not results:
496 if not results:
483 msg = _('server ignored update of %s to public!\n') % node
497 msg = _('server ignored update of %s to public!\n') % node
484 elif not int(results[0]['return']):
498 elif not int(results[0]['return']):
485 msg = _('updating %s to public failed!\n') % node
499 msg = _('updating %s to public failed!\n') % node
486 if msg is not None:
500 if msg is not None:
487 pushop.ui.warn(msg)
501 pushop.ui.warn(msg)
488 return handlereply
502 return handlereply
489
503
490 @b2partsgenerator('obsmarkers')
504 @b2partsgenerator('obsmarkers')
491 def _pushb2obsmarkers(pushop, bundler):
505 def _pushb2obsmarkers(pushop, bundler):
492 if 'obsmarkers' in pushop.stepsdone:
506 if 'obsmarkers' in pushop.stepsdone:
493 return
507 return
494 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
508 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
495 if obsolete.commonversion(remoteversions) is None:
509 if obsolete.commonversion(remoteversions) is None:
496 return
510 return
497 pushop.stepsdone.add('obsmarkers')
511 pushop.stepsdone.add('obsmarkers')
498 if pushop.outobsmarkers:
512 if pushop.outobsmarkers:
499 buildobsmarkerspart(bundler, pushop.outobsmarkers)
513 buildobsmarkerspart(bundler, pushop.outobsmarkers)
500
514
501 @b2partsgenerator('bookmarks')
515 @b2partsgenerator('bookmarks')
502 def _pushb2bookmarks(pushop, bundler):
516 def _pushb2bookmarks(pushop, bundler):
503 """handle phase push through bundle2"""
517 """handle phase push through bundle2"""
504 if 'bookmarks' in pushop.stepsdone:
518 if 'bookmarks' in pushop.stepsdone:
505 return
519 return
506 b2caps = bundle2.bundle2caps(pushop.remote)
520 b2caps = bundle2.bundle2caps(pushop.remote)
507 if 'b2x:pushkey' not in b2caps:
521 if 'b2x:pushkey' not in b2caps:
508 return
522 return
509 pushop.stepsdone.add('bookmarks')
523 pushop.stepsdone.add('bookmarks')
510 part2book = []
524 part2book = []
511 enc = pushkey.encode
525 enc = pushkey.encode
512 for book, old, new in pushop.outbookmarks:
526 for book, old, new in pushop.outbookmarks:
513 part = bundler.newpart('b2x:pushkey')
527 part = bundler.newpart('b2x:pushkey')
514 part.addparam('namespace', enc('bookmarks'))
528 part.addparam('namespace', enc('bookmarks'))
515 part.addparam('key', enc(book))
529 part.addparam('key', enc(book))
516 part.addparam('old', enc(old))
530 part.addparam('old', enc(old))
517 part.addparam('new', enc(new))
531 part.addparam('new', enc(new))
518 action = 'update'
532 action = 'update'
519 if not old:
533 if not old:
520 action = 'export'
534 action = 'export'
521 elif not new:
535 elif not new:
522 action = 'delete'
536 action = 'delete'
523 part2book.append((part.id, book, action))
537 part2book.append((part.id, book, action))
524
538
525
539
526 def handlereply(op):
540 def handlereply(op):
527 ui = pushop.ui
541 ui = pushop.ui
528 for partid, book, action in part2book:
542 for partid, book, action in part2book:
529 partrep = op.records.getreplies(partid)
543 partrep = op.records.getreplies(partid)
530 results = partrep['pushkey']
544 results = partrep['pushkey']
531 assert len(results) <= 1
545 assert len(results) <= 1
532 if not results:
546 if not results:
533 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
547 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
534 else:
548 else:
535 ret = int(results[0]['return'])
549 ret = int(results[0]['return'])
536 if ret:
550 if ret:
537 ui.status(bookmsgmap[action][0] % book)
551 ui.status(bookmsgmap[action][0] % book)
538 else:
552 else:
539 ui.warn(bookmsgmap[action][1] % book)
553 ui.warn(bookmsgmap[action][1] % book)
540 if pushop.bkresult is not None:
554 if pushop.bkresult is not None:
541 pushop.bkresult = 1
555 pushop.bkresult = 1
542 return handlereply
556 return handlereply
543
557
544
558
545 def _pushbundle2(pushop):
559 def _pushbundle2(pushop):
546 """push data to the remote using bundle2
560 """push data to the remote using bundle2
547
561
548 The only currently supported type of data is changegroup but this will
562 The only currently supported type of data is changegroup but this will
549 evolve in the future."""
563 evolve in the future."""
550 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
564 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
551 # create reply capability
565 # create reply capability
552 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
566 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
553 bundler.newpart('b2x:replycaps', data=capsblob)
567 bundler.newpart('b2x:replycaps', data=capsblob)
554 replyhandlers = []
568 replyhandlers = []
555 for partgenname in b2partsgenorder:
569 for partgenname in b2partsgenorder:
556 partgen = b2partsgenmapping[partgenname]
570 partgen = b2partsgenmapping[partgenname]
557 ret = partgen(pushop, bundler)
571 ret = partgen(pushop, bundler)
558 if callable(ret):
572 if callable(ret):
559 replyhandlers.append(ret)
573 replyhandlers.append(ret)
560 # do not push if nothing to push
574 # do not push if nothing to push
561 if bundler.nbparts <= 1:
575 if bundler.nbparts <= 1:
562 return
576 return
563 stream = util.chunkbuffer(bundler.getchunks())
577 stream = util.chunkbuffer(bundler.getchunks())
564 try:
578 try:
565 reply = pushop.remote.unbundle(stream, ['force'], 'push')
579 reply = pushop.remote.unbundle(stream, ['force'], 'push')
566 except error.BundleValueError, exc:
580 except error.BundleValueError, exc:
567 raise util.Abort('missing support for %s' % exc)
581 raise util.Abort('missing support for %s' % exc)
568 try:
582 try:
569 op = bundle2.processbundle(pushop.repo, reply)
583 op = bundle2.processbundle(pushop.repo, reply)
570 except error.BundleValueError, exc:
584 except error.BundleValueError, exc:
571 raise util.Abort('missing support for %s' % exc)
585 raise util.Abort('missing support for %s' % exc)
572 for rephand in replyhandlers:
586 for rephand in replyhandlers:
573 rephand(op)
587 rephand(op)
574
588
575 def _pushchangeset(pushop):
589 def _pushchangeset(pushop):
576 """Make the actual push of changeset bundle to remote repo"""
590 """Make the actual push of changeset bundle to remote repo"""
577 if 'changesets' in pushop.stepsdone:
591 if 'changesets' in pushop.stepsdone:
578 return
592 return
579 pushop.stepsdone.add('changesets')
593 pushop.stepsdone.add('changesets')
580 if not _pushcheckoutgoing(pushop):
594 if not _pushcheckoutgoing(pushop):
581 return
595 return
582 pushop.repo.prepushoutgoinghooks(pushop.repo,
596 pushop.repo.prepushoutgoinghooks(pushop.repo,
583 pushop.remote,
597 pushop.remote,
584 pushop.outgoing)
598 pushop.outgoing)
585 outgoing = pushop.outgoing
599 outgoing = pushop.outgoing
586 unbundle = pushop.remote.capable('unbundle')
600 unbundle = pushop.remote.capable('unbundle')
587 # TODO: get bundlecaps from remote
601 # TODO: get bundlecaps from remote
588 bundlecaps = None
602 bundlecaps = None
589 # create a changegroup from local
603 # create a changegroup from local
590 if pushop.revs is None and not (outgoing.excluded
604 if pushop.revs is None and not (outgoing.excluded
591 or pushop.repo.changelog.filteredrevs):
605 or pushop.repo.changelog.filteredrevs):
592 # push everything,
606 # push everything,
593 # use the fast path, no race possible on push
607 # use the fast path, no race possible on push
594 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
608 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
595 cg = changegroup.getsubset(pushop.repo,
609 cg = changegroup.getsubset(pushop.repo,
596 outgoing,
610 outgoing,
597 bundler,
611 bundler,
598 'push',
612 'push',
599 fastpath=True)
613 fastpath=True)
600 else:
614 else:
601 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
615 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
602 bundlecaps)
616 bundlecaps)
603
617
604 # apply changegroup to remote
618 # apply changegroup to remote
605 if unbundle:
619 if unbundle:
606 # local repo finds heads on server, finds out what
620 # local repo finds heads on server, finds out what
607 # revs it must push. once revs transferred, if server
621 # revs it must push. once revs transferred, if server
608 # finds it has different heads (someone else won
622 # finds it has different heads (someone else won
609 # commit/push race), server aborts.
623 # commit/push race), server aborts.
610 if pushop.force:
624 if pushop.force:
611 remoteheads = ['force']
625 remoteheads = ['force']
612 else:
626 else:
613 remoteheads = pushop.remoteheads
627 remoteheads = pushop.remoteheads
614 # ssh: return remote's addchangegroup()
628 # ssh: return remote's addchangegroup()
615 # http: return remote's addchangegroup() or 0 for error
629 # http: return remote's addchangegroup() or 0 for error
616 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
630 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
617 pushop.repo.url())
631 pushop.repo.url())
618 else:
632 else:
619 # we return an integer indicating remote head count
633 # we return an integer indicating remote head count
620 # change
634 # change
621 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
635 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
622 pushop.repo.url())
636 pushop.repo.url())
623
637
624 def _pushsyncphase(pushop):
638 def _pushsyncphase(pushop):
625 """synchronise phase information locally and remotely"""
639 """synchronise phase information locally and remotely"""
626 cheads = pushop.commonheads
640 cheads = pushop.commonheads
627 # even when we don't push, exchanging phase data is useful
641 # even when we don't push, exchanging phase data is useful
628 remotephases = pushop.remote.listkeys('phases')
642 remotephases = pushop.remote.listkeys('phases')
629 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
643 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
630 and remotephases # server supports phases
644 and remotephases # server supports phases
631 and pushop.cgresult is None # nothing was pushed
645 and pushop.cgresult is None # nothing was pushed
632 and remotephases.get('publishing', False)):
646 and remotephases.get('publishing', False)):
633 # When:
647 # When:
634 # - this is a subrepo push
648 # - this is a subrepo push
635 # - and remote support phase
649 # - and remote support phase
636 # - and no changeset was pushed
650 # - and no changeset was pushed
637 # - and remote is publishing
651 # - and remote is publishing
638 # We may be in issue 3871 case!
652 # We may be in issue 3871 case!
639 # We drop the possible phase synchronisation done by
653 # We drop the possible phase synchronisation done by
640 # courtesy to publish changesets possibly locally draft
654 # courtesy to publish changesets possibly locally draft
641 # on the remote.
655 # on the remote.
642 remotephases = {'publishing': 'True'}
656 remotephases = {'publishing': 'True'}
643 if not remotephases: # old server or public only reply from non-publishing
657 if not remotephases: # old server or public only reply from non-publishing
644 _localphasemove(pushop, cheads)
658 _localphasemove(pushop, cheads)
645 # don't push any phase data as there is nothing to push
659 # don't push any phase data as there is nothing to push
646 else:
660 else:
647 ana = phases.analyzeremotephases(pushop.repo, cheads,
661 ana = phases.analyzeremotephases(pushop.repo, cheads,
648 remotephases)
662 remotephases)
649 pheads, droots = ana
663 pheads, droots = ana
650 ### Apply remote phase on local
664 ### Apply remote phase on local
651 if remotephases.get('publishing', False):
665 if remotephases.get('publishing', False):
652 _localphasemove(pushop, cheads)
666 _localphasemove(pushop, cheads)
653 else: # publish = False
667 else: # publish = False
654 _localphasemove(pushop, pheads)
668 _localphasemove(pushop, pheads)
655 _localphasemove(pushop, cheads, phases.draft)
669 _localphasemove(pushop, cheads, phases.draft)
656 ### Apply local phase on remote
670 ### Apply local phase on remote
657
671
658 if pushop.cgresult:
672 if pushop.cgresult:
659 if 'phases' in pushop.stepsdone:
673 if 'phases' in pushop.stepsdone:
660 # phases already pushed though bundle2
674 # phases already pushed though bundle2
661 return
675 return
662 outdated = pushop.outdatedphases
676 outdated = pushop.outdatedphases
663 else:
677 else:
664 outdated = pushop.fallbackoutdatedphases
678 outdated = pushop.fallbackoutdatedphases
665
679
666 pushop.stepsdone.add('phases')
680 pushop.stepsdone.add('phases')
667
681
668 # filter heads already turned public by the push
682 # filter heads already turned public by the push
669 outdated = [c for c in outdated if c.node() not in pheads]
683 outdated = [c for c in outdated if c.node() not in pheads]
670 b2caps = bundle2.bundle2caps(pushop.remote)
684 b2caps = bundle2.bundle2caps(pushop.remote)
671 if 'b2x:pushkey' in b2caps:
685 if 'b2x:pushkey' in b2caps:
672 # server supports bundle2, let's do a batched push through it
686 # server supports bundle2, let's do a batched push through it
673 #
687 #
674 # This will eventually be unified with the changesets bundle2 push
688 # This will eventually be unified with the changesets bundle2 push
675 bundler = bundle2.bundle20(pushop.ui, b2caps)
689 bundler = bundle2.bundle20(pushop.ui, b2caps)
676 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
690 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
677 bundler.newpart('b2x:replycaps', data=capsblob)
691 bundler.newpart('b2x:replycaps', data=capsblob)
678 part2node = []
692 part2node = []
679 enc = pushkey.encode
693 enc = pushkey.encode
680 for newremotehead in outdated:
694 for newremotehead in outdated:
681 part = bundler.newpart('b2x:pushkey')
695 part = bundler.newpart('b2x:pushkey')
682 part.addparam('namespace', enc('phases'))
696 part.addparam('namespace', enc('phases'))
683 part.addparam('key', enc(newremotehead.hex()))
697 part.addparam('key', enc(newremotehead.hex()))
684 part.addparam('old', enc(str(phases.draft)))
698 part.addparam('old', enc(str(phases.draft)))
685 part.addparam('new', enc(str(phases.public)))
699 part.addparam('new', enc(str(phases.public)))
686 part2node.append((part.id, newremotehead))
700 part2node.append((part.id, newremotehead))
687 stream = util.chunkbuffer(bundler.getchunks())
701 stream = util.chunkbuffer(bundler.getchunks())
688 try:
702 try:
689 reply = pushop.remote.unbundle(stream, ['force'], 'push')
703 reply = pushop.remote.unbundle(stream, ['force'], 'push')
690 op = bundle2.processbundle(pushop.repo, reply)
704 op = bundle2.processbundle(pushop.repo, reply)
691 except error.BundleValueError, exc:
705 except error.BundleValueError, exc:
692 raise util.Abort('missing support for %s' % exc)
706 raise util.Abort('missing support for %s' % exc)
693 for partid, node in part2node:
707 for partid, node in part2node:
694 partrep = op.records.getreplies(partid)
708 partrep = op.records.getreplies(partid)
695 results = partrep['pushkey']
709 results = partrep['pushkey']
696 assert len(results) <= 1
710 assert len(results) <= 1
697 msg = None
711 msg = None
698 if not results:
712 if not results:
699 msg = _('server ignored update of %s to public!\n') % node
713 msg = _('server ignored update of %s to public!\n') % node
700 elif not int(results[0]['return']):
714 elif not int(results[0]['return']):
701 msg = _('updating %s to public failed!\n') % node
715 msg = _('updating %s to public failed!\n') % node
702 if msg is not None:
716 if msg is not None:
703 pushop.ui.warn(msg)
717 pushop.ui.warn(msg)
704
718
705 else:
719 else:
706 # fallback to independent pushkey command
720 # fallback to independent pushkey command
707 for newremotehead in outdated:
721 for newremotehead in outdated:
708 r = pushop.remote.pushkey('phases',
722 r = pushop.remote.pushkey('phases',
709 newremotehead.hex(),
723 newremotehead.hex(),
710 str(phases.draft),
724 str(phases.draft),
711 str(phases.public))
725 str(phases.public))
712 if not r:
726 if not r:
713 pushop.ui.warn(_('updating %s to public failed!\n')
727 pushop.ui.warn(_('updating %s to public failed!\n')
714 % newremotehead)
728 % newremotehead)
715
729
716 def _localphasemove(pushop, nodes, phase=phases.public):
730 def _localphasemove(pushop, nodes, phase=phases.public):
717 """move <nodes> to <phase> in the local source repo"""
731 """move <nodes> to <phase> in the local source repo"""
718 if pushop.locallocked:
732 if pushop.locallocked:
719 tr = pushop.repo.transaction('push-phase-sync')
733 tr = pushop.repo.transaction('push-phase-sync')
720 try:
734 try:
721 phases.advanceboundary(pushop.repo, tr, phase, nodes)
735 phases.advanceboundary(pushop.repo, tr, phase, nodes)
722 tr.close()
736 tr.close()
723 finally:
737 finally:
724 tr.release()
738 tr.release()
725 else:
739 else:
726 # repo is not locked, do not change any phases!
740 # repo is not locked, do not change any phases!
727 # Informs the user that phases should have been moved when
741 # Informs the user that phases should have been moved when
728 # applicable.
742 # applicable.
729 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
743 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
730 phasestr = phases.phasenames[phase]
744 phasestr = phases.phasenames[phase]
731 if actualmoves:
745 if actualmoves:
732 pushop.ui.status(_('cannot lock source repo, skipping '
746 pushop.ui.status(_('cannot lock source repo, skipping '
733 'local %s phase update\n') % phasestr)
747 'local %s phase update\n') % phasestr)
734
748
735 def _pushobsolete(pushop):
749 def _pushobsolete(pushop):
736 """utility function to push obsolete markers to a remote"""
750 """utility function to push obsolete markers to a remote"""
737 if 'obsmarkers' in pushop.stepsdone:
751 if 'obsmarkers' in pushop.stepsdone:
738 return
752 return
739 pushop.ui.debug('try to push obsolete markers to remote\n')
753 pushop.ui.debug('try to push obsolete markers to remote\n')
740 repo = pushop.repo
754 repo = pushop.repo
741 remote = pushop.remote
755 remote = pushop.remote
742 pushop.stepsdone.add('obsmarkers')
756 pushop.stepsdone.add('obsmarkers')
743 if pushop.outobsmarkers:
757 if pushop.outobsmarkers:
744 rslts = []
758 rslts = []
745 remotedata = obsolete._pushkeyescape(pushop.outobsmarkers)
759 remotedata = obsolete._pushkeyescape(pushop.outobsmarkers)
746 for key in sorted(remotedata, reverse=True):
760 for key in sorted(remotedata, reverse=True):
747 # reverse sort to ensure we end with dump0
761 # reverse sort to ensure we end with dump0
748 data = remotedata[key]
762 data = remotedata[key]
749 rslts.append(remote.pushkey('obsolete', key, '', data))
763 rslts.append(remote.pushkey('obsolete', key, '', data))
750 if [r for r in rslts if not r]:
764 if [r for r in rslts if not r]:
751 msg = _('failed to push some obsolete markers!\n')
765 msg = _('failed to push some obsolete markers!\n')
752 repo.ui.warn(msg)
766 repo.ui.warn(msg)
753
767
754 def _pushbookmark(pushop):
768 def _pushbookmark(pushop):
755 """Update bookmark position on remote"""
769 """Update bookmark position on remote"""
756 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
770 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
757 return
771 return
758 pushop.stepsdone.add('bookmarks')
772 pushop.stepsdone.add('bookmarks')
759 ui = pushop.ui
773 ui = pushop.ui
760 remote = pushop.remote
774 remote = pushop.remote
761
775
762 for b, old, new in pushop.outbookmarks:
776 for b, old, new in pushop.outbookmarks:
763 action = 'update'
777 action = 'update'
764 if not old:
778 if not old:
765 action = 'export'
779 action = 'export'
766 elif not new:
780 elif not new:
767 action = 'delete'
781 action = 'delete'
768 if remote.pushkey('bookmarks', b, old, new):
782 if remote.pushkey('bookmarks', b, old, new):
769 ui.status(bookmsgmap[action][0] % b)
783 ui.status(bookmsgmap[action][0] % b)
770 else:
784 else:
771 ui.warn(bookmsgmap[action][1] % b)
785 ui.warn(bookmsgmap[action][1] % b)
772 # discovery can have set the value form invalid entry
786 # discovery can have set the value form invalid entry
773 if pushop.bkresult is not None:
787 if pushop.bkresult is not None:
774 pushop.bkresult = 1
788 pushop.bkresult = 1
775
789
776 class pulloperation(object):
790 class pulloperation(object):
777 """A object that represent a single pull operation
791 """A object that represent a single pull operation
778
792
779 It purpose is to carry push related state and very common operation.
793 It purpose is to carry push related state and very common operation.
780
794
781 A new should be created at the beginning of each pull and discarded
795 A new should be created at the beginning of each pull and discarded
782 afterward.
796 afterward.
783 """
797 """
784
798
785 def __init__(self, repo, remote, heads=None, force=False, bookmarks=()):
799 def __init__(self, repo, remote, heads=None, force=False, bookmarks=()):
786 # repo we pull into
800 # repo we pull into
787 self.repo = repo
801 self.repo = repo
788 # repo we pull from
802 # repo we pull from
789 self.remote = remote
803 self.remote = remote
790 # revision we try to pull (None is "all")
804 # revision we try to pull (None is "all")
791 self.heads = heads
805 self.heads = heads
792 # bookmark pulled explicitly
806 # bookmark pulled explicitly
793 self.explicitbookmarks = bookmarks
807 self.explicitbookmarks = bookmarks
794 # do we force pull?
808 # do we force pull?
795 self.force = force
809 self.force = force
796 # the name the pull transaction
810 # the name the pull transaction
797 self._trname = 'pull\n' + util.hidepassword(remote.url())
811 self._trname = 'pull\n' + util.hidepassword(remote.url())
798 # hold the transaction once created
812 # hold the transaction once created
799 self._tr = None
813 self._tr = None
800 # set of common changeset between local and remote before pull
814 # set of common changeset between local and remote before pull
801 self.common = None
815 self.common = None
802 # set of pulled head
816 # set of pulled head
803 self.rheads = None
817 self.rheads = None
804 # list of missing changeset to fetch remotely
818 # list of missing changeset to fetch remotely
805 self.fetch = None
819 self.fetch = None
806 # remote bookmarks data
820 # remote bookmarks data
807 self.remotebookmarks = None
821 self.remotebookmarks = None
808 # result of changegroup pulling (used as return code by pull)
822 # result of changegroup pulling (used as return code by pull)
809 self.cgresult = None
823 self.cgresult = None
810 # list of step already done
824 # list of step already done
811 self.stepsdone = set()
825 self.stepsdone = set()
812
826
813 @util.propertycache
827 @util.propertycache
814 def pulledsubset(self):
828 def pulledsubset(self):
815 """heads of the set of changeset target by the pull"""
829 """heads of the set of changeset target by the pull"""
816 # compute target subset
830 # compute target subset
817 if self.heads is None:
831 if self.heads is None:
818 # We pulled every thing possible
832 # We pulled every thing possible
819 # sync on everything common
833 # sync on everything common
820 c = set(self.common)
834 c = set(self.common)
821 ret = list(self.common)
835 ret = list(self.common)
822 for n in self.rheads:
836 for n in self.rheads:
823 if n not in c:
837 if n not in c:
824 ret.append(n)
838 ret.append(n)
825 return ret
839 return ret
826 else:
840 else:
827 # We pulled a specific subset
841 # We pulled a specific subset
828 # sync on this subset
842 # sync on this subset
829 return self.heads
843 return self.heads
830
844
831 def gettransaction(self):
845 def gettransaction(self):
832 """get appropriate pull transaction, creating it if needed"""
846 """get appropriate pull transaction, creating it if needed"""
833 if self._tr is None:
847 if self._tr is None:
834 self._tr = self.repo.transaction(self._trname)
848 self._tr = self.repo.transaction(self._trname)
835 self._tr.hookargs['source'] = 'pull'
849 self._tr.hookargs['source'] = 'pull'
836 self._tr.hookargs['url'] = self.remote.url()
850 self._tr.hookargs['url'] = self.remote.url()
837 return self._tr
851 return self._tr
838
852
839 def closetransaction(self):
853 def closetransaction(self):
840 """close transaction if created"""
854 """close transaction if created"""
841 if self._tr is not None:
855 if self._tr is not None:
842 repo = self.repo
856 repo = self.repo
843 cl = repo.unfiltered().changelog
857 cl = repo.unfiltered().changelog
844 p = cl.writepending() and repo.root or ""
858 p = cl.writepending() and repo.root or ""
845 p = cl.writepending() and repo.root or ""
859 p = cl.writepending() and repo.root or ""
846 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
860 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
847 **self._tr.hookargs)
861 **self._tr.hookargs)
848 self._tr.close()
862 self._tr.close()
849 hookargs = dict(self._tr.hookargs)
863 hookargs = dict(self._tr.hookargs)
850 def runhooks():
864 def runhooks():
851 repo.hook('b2x-transactionclose', **hookargs)
865 repo.hook('b2x-transactionclose', **hookargs)
852 repo._afterlock(runhooks)
866 repo._afterlock(runhooks)
853
867
854 def releasetransaction(self):
868 def releasetransaction(self):
855 """release transaction if created"""
869 """release transaction if created"""
856 if self._tr is not None:
870 if self._tr is not None:
857 self._tr.release()
871 self._tr.release()
858
872
859 def pull(repo, remote, heads=None, force=False, bookmarks=()):
873 def pull(repo, remote, heads=None, force=False, bookmarks=()):
860 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks)
874 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks)
861 if pullop.remote.local():
875 if pullop.remote.local():
862 missing = set(pullop.remote.requirements) - pullop.repo.supported
876 missing = set(pullop.remote.requirements) - pullop.repo.supported
863 if missing:
877 if missing:
864 msg = _("required features are not"
878 msg = _("required features are not"
865 " supported in the destination:"
879 " supported in the destination:"
866 " %s") % (', '.join(sorted(missing)))
880 " %s") % (', '.join(sorted(missing)))
867 raise util.Abort(msg)
881 raise util.Abort(msg)
868
882
869 pullop.remotebookmarks = remote.listkeys('bookmarks')
883 pullop.remotebookmarks = remote.listkeys('bookmarks')
870 lock = pullop.repo.lock()
884 lock = pullop.repo.lock()
871 try:
885 try:
872 _pulldiscovery(pullop)
886 _pulldiscovery(pullop)
873 if (pullop.repo.ui.configbool('experimental', 'bundle2-exp', False)
887 if (pullop.repo.ui.configbool('experimental', 'bundle2-exp', False)
874 and pullop.remote.capable('bundle2-exp')):
888 and pullop.remote.capable('bundle2-exp')):
875 _pullbundle2(pullop)
889 _pullbundle2(pullop)
876 _pullchangeset(pullop)
890 _pullchangeset(pullop)
877 _pullphase(pullop)
891 _pullphase(pullop)
878 _pullbookmarks(pullop)
892 _pullbookmarks(pullop)
879 _pullobsolete(pullop)
893 _pullobsolete(pullop)
880 pullop.closetransaction()
894 pullop.closetransaction()
881 finally:
895 finally:
882 pullop.releasetransaction()
896 pullop.releasetransaction()
883 lock.release()
897 lock.release()
884
898
885 return pullop
899 return pullop
886
900
887 # list of steps to perform discovery before pull
901 # list of steps to perform discovery before pull
888 pulldiscoveryorder = []
902 pulldiscoveryorder = []
889
903
890 # Mapping between step name and function
904 # Mapping between step name and function
891 #
905 #
892 # This exists to help extensions wrap steps if necessary
906 # This exists to help extensions wrap steps if necessary
893 pulldiscoverymapping = {}
907 pulldiscoverymapping = {}
894
908
895 def pulldiscovery(stepname):
909 def pulldiscovery(stepname):
896 """decorator for function performing discovery before pull
910 """decorator for function performing discovery before pull
897
911
898 The function is added to the step -> function mapping and appended to the
912 The function is added to the step -> function mapping and appended to the
899 list of steps. Beware that decorated function will be added in order (this
913 list of steps. Beware that decorated function will be added in order (this
900 may matter).
914 may matter).
901
915
902 You can only use this decorator for a new step, if you want to wrap a step
916 You can only use this decorator for a new step, if you want to wrap a step
903 from an extension, change the pulldiscovery dictionary directly."""
917 from an extension, change the pulldiscovery dictionary directly."""
904 def dec(func):
918 def dec(func):
905 assert stepname not in pulldiscoverymapping
919 assert stepname not in pulldiscoverymapping
906 pulldiscoverymapping[stepname] = func
920 pulldiscoverymapping[stepname] = func
907 pulldiscoveryorder.append(stepname)
921 pulldiscoveryorder.append(stepname)
908 return func
922 return func
909 return dec
923 return dec
910
924
911 def _pulldiscovery(pullop):
925 def _pulldiscovery(pullop):
912 """Run all discovery steps"""
926 """Run all discovery steps"""
913 for stepname in pulldiscoveryorder:
927 for stepname in pulldiscoveryorder:
914 step = pulldiscoverymapping[stepname]
928 step = pulldiscoverymapping[stepname]
915 step(pullop)
929 step(pullop)
916
930
917 @pulldiscovery('changegroup')
931 @pulldiscovery('changegroup')
918 def _pulldiscoverychangegroup(pullop):
932 def _pulldiscoverychangegroup(pullop):
919 """discovery phase for the pull
933 """discovery phase for the pull
920
934
921 Current handle changeset discovery only, will change handle all discovery
935 Current handle changeset discovery only, will change handle all discovery
922 at some point."""
936 at some point."""
923 tmp = discovery.findcommonincoming(pullop.repo.unfiltered(),
937 tmp = discovery.findcommonincoming(pullop.repo.unfiltered(),
924 pullop.remote,
938 pullop.remote,
925 heads=pullop.heads,
939 heads=pullop.heads,
926 force=pullop.force)
940 force=pullop.force)
927 pullop.common, pullop.fetch, pullop.rheads = tmp
941 pullop.common, pullop.fetch, pullop.rheads = tmp
928
942
929 def _pullbundle2(pullop):
943 def _pullbundle2(pullop):
930 """pull data using bundle2
944 """pull data using bundle2
931
945
932 For now, the only supported data are changegroup."""
946 For now, the only supported data are changegroup."""
933 remotecaps = bundle2.bundle2caps(pullop.remote)
947 remotecaps = bundle2.bundle2caps(pullop.remote)
934 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
948 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
935 # pulling changegroup
949 # pulling changegroup
936 pullop.stepsdone.add('changegroup')
950 pullop.stepsdone.add('changegroup')
937
951
938 kwargs['common'] = pullop.common
952 kwargs['common'] = pullop.common
939 kwargs['heads'] = pullop.heads or pullop.rheads
953 kwargs['heads'] = pullop.heads or pullop.rheads
940 kwargs['cg'] = pullop.fetch
954 kwargs['cg'] = pullop.fetch
941 if 'b2x:listkeys' in remotecaps:
955 if 'b2x:listkeys' in remotecaps:
942 kwargs['listkeys'] = ['phase', 'bookmarks']
956 kwargs['listkeys'] = ['phase', 'bookmarks']
943 if not pullop.fetch:
957 if not pullop.fetch:
944 pullop.repo.ui.status(_("no changes found\n"))
958 pullop.repo.ui.status(_("no changes found\n"))
945 pullop.cgresult = 0
959 pullop.cgresult = 0
946 else:
960 else:
947 if pullop.heads is None and list(pullop.common) == [nullid]:
961 if pullop.heads is None and list(pullop.common) == [nullid]:
948 pullop.repo.ui.status(_("requesting all changes\n"))
962 pullop.repo.ui.status(_("requesting all changes\n"))
949 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
963 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
950 remoteversions = bundle2.obsmarkersversion(remotecaps)
964 remoteversions = bundle2.obsmarkersversion(remotecaps)
951 if obsolete.commonversion(remoteversions) is not None:
965 if obsolete.commonversion(remoteversions) is not None:
952 kwargs['obsmarkers'] = True
966 kwargs['obsmarkers'] = True
953 pullop.stepsdone.add('obsmarkers')
967 pullop.stepsdone.add('obsmarkers')
954 _pullbundle2extraprepare(pullop, kwargs)
968 _pullbundle2extraprepare(pullop, kwargs)
955 if kwargs.keys() == ['format']:
969 if kwargs.keys() == ['format']:
956 return # nothing to pull
970 return # nothing to pull
957 bundle = pullop.remote.getbundle('pull', **kwargs)
971 bundle = pullop.remote.getbundle('pull', **kwargs)
958 try:
972 try:
959 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
973 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
960 except error.BundleValueError, exc:
974 except error.BundleValueError, exc:
961 raise util.Abort('missing support for %s' % exc)
975 raise util.Abort('missing support for %s' % exc)
962
976
963 if pullop.fetch:
977 if pullop.fetch:
964 changedheads = 0
978 changedheads = 0
965 pullop.cgresult = 1
979 pullop.cgresult = 1
966 for cg in op.records['changegroup']:
980 for cg in op.records['changegroup']:
967 ret = cg['return']
981 ret = cg['return']
968 # If any changegroup result is 0, return 0
982 # If any changegroup result is 0, return 0
969 if ret == 0:
983 if ret == 0:
970 pullop.cgresult = 0
984 pullop.cgresult = 0
971 break
985 break
972 if ret < -1:
986 if ret < -1:
973 changedheads += ret + 1
987 changedheads += ret + 1
974 elif ret > 1:
988 elif ret > 1:
975 changedheads += ret - 1
989 changedheads += ret - 1
976 if changedheads > 0:
990 if changedheads > 0:
977 pullop.cgresult = 1 + changedheads
991 pullop.cgresult = 1 + changedheads
978 elif changedheads < 0:
992 elif changedheads < 0:
979 pullop.cgresult = -1 + changedheads
993 pullop.cgresult = -1 + changedheads
980
994
981 # processing phases change
995 # processing phases change
982 for namespace, value in op.records['listkeys']:
996 for namespace, value in op.records['listkeys']:
983 if namespace == 'phases':
997 if namespace == 'phases':
984 _pullapplyphases(pullop, value)
998 _pullapplyphases(pullop, value)
985
999
986 # processing bookmark update
1000 # processing bookmark update
987 for namespace, value in op.records['listkeys']:
1001 for namespace, value in op.records['listkeys']:
988 if namespace == 'bookmarks':
1002 if namespace == 'bookmarks':
989 pullop.remotebookmarks = value
1003 pullop.remotebookmarks = value
990 _pullbookmarks(pullop)
1004 _pullbookmarks(pullop)
991
1005
992 def _pullbundle2extraprepare(pullop, kwargs):
1006 def _pullbundle2extraprepare(pullop, kwargs):
993 """hook function so that extensions can extend the getbundle call"""
1007 """hook function so that extensions can extend the getbundle call"""
994 pass
1008 pass
995
1009
996 def _pullchangeset(pullop):
1010 def _pullchangeset(pullop):
997 """pull changeset from unbundle into the local repo"""
1011 """pull changeset from unbundle into the local repo"""
998 # We delay the open of the transaction as late as possible so we
1012 # We delay the open of the transaction as late as possible so we
999 # don't open transaction for nothing or you break future useful
1013 # don't open transaction for nothing or you break future useful
1000 # rollback call
1014 # rollback call
1001 if 'changegroup' in pullop.stepsdone:
1015 if 'changegroup' in pullop.stepsdone:
1002 return
1016 return
1003 pullop.stepsdone.add('changegroup')
1017 pullop.stepsdone.add('changegroup')
1004 if not pullop.fetch:
1018 if not pullop.fetch:
1005 pullop.repo.ui.status(_("no changes found\n"))
1019 pullop.repo.ui.status(_("no changes found\n"))
1006 pullop.cgresult = 0
1020 pullop.cgresult = 0
1007 return
1021 return
1008 pullop.gettransaction()
1022 pullop.gettransaction()
1009 if pullop.heads is None and list(pullop.common) == [nullid]:
1023 if pullop.heads is None and list(pullop.common) == [nullid]:
1010 pullop.repo.ui.status(_("requesting all changes\n"))
1024 pullop.repo.ui.status(_("requesting all changes\n"))
1011 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1025 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1012 # issue1320, avoid a race if remote changed after discovery
1026 # issue1320, avoid a race if remote changed after discovery
1013 pullop.heads = pullop.rheads
1027 pullop.heads = pullop.rheads
1014
1028
1015 if pullop.remote.capable('getbundle'):
1029 if pullop.remote.capable('getbundle'):
1016 # TODO: get bundlecaps from remote
1030 # TODO: get bundlecaps from remote
1017 cg = pullop.remote.getbundle('pull', common=pullop.common,
1031 cg = pullop.remote.getbundle('pull', common=pullop.common,
1018 heads=pullop.heads or pullop.rheads)
1032 heads=pullop.heads or pullop.rheads)
1019 elif pullop.heads is None:
1033 elif pullop.heads is None:
1020 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1034 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1021 elif not pullop.remote.capable('changegroupsubset'):
1035 elif not pullop.remote.capable('changegroupsubset'):
1022 raise util.Abort(_("partial pull cannot be done because "
1036 raise util.Abort(_("partial pull cannot be done because "
1023 "other repository doesn't support "
1037 "other repository doesn't support "
1024 "changegroupsubset."))
1038 "changegroupsubset."))
1025 else:
1039 else:
1026 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1040 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1027 pullop.cgresult = changegroup.addchangegroup(pullop.repo, cg, 'pull',
1041 pullop.cgresult = changegroup.addchangegroup(pullop.repo, cg, 'pull',
1028 pullop.remote.url())
1042 pullop.remote.url())
1029
1043
1030 def _pullphase(pullop):
1044 def _pullphase(pullop):
1031 # Get remote phases data from remote
1045 # Get remote phases data from remote
1032 if 'phases' in pullop.stepsdone:
1046 if 'phases' in pullop.stepsdone:
1033 return
1047 return
1034 remotephases = pullop.remote.listkeys('phases')
1048 remotephases = pullop.remote.listkeys('phases')
1035 _pullapplyphases(pullop, remotephases)
1049 _pullapplyphases(pullop, remotephases)
1036
1050
1037 def _pullapplyphases(pullop, remotephases):
1051 def _pullapplyphases(pullop, remotephases):
1038 """apply phase movement from observed remote state"""
1052 """apply phase movement from observed remote state"""
1039 if 'phases' in pullop.stepsdone:
1053 if 'phases' in pullop.stepsdone:
1040 return
1054 return
1041 pullop.stepsdone.add('phases')
1055 pullop.stepsdone.add('phases')
1042 publishing = bool(remotephases.get('publishing', False))
1056 publishing = bool(remotephases.get('publishing', False))
1043 if remotephases and not publishing:
1057 if remotephases and not publishing:
1044 # remote is new and unpublishing
1058 # remote is new and unpublishing
1045 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1059 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1046 pullop.pulledsubset,
1060 pullop.pulledsubset,
1047 remotephases)
1061 remotephases)
1048 dheads = pullop.pulledsubset
1062 dheads = pullop.pulledsubset
1049 else:
1063 else:
1050 # Remote is old or publishing all common changesets
1064 # Remote is old or publishing all common changesets
1051 # should be seen as public
1065 # should be seen as public
1052 pheads = pullop.pulledsubset
1066 pheads = pullop.pulledsubset
1053 dheads = []
1067 dheads = []
1054 unfi = pullop.repo.unfiltered()
1068 unfi = pullop.repo.unfiltered()
1055 phase = unfi._phasecache.phase
1069 phase = unfi._phasecache.phase
1056 rev = unfi.changelog.nodemap.get
1070 rev = unfi.changelog.nodemap.get
1057 public = phases.public
1071 public = phases.public
1058 draft = phases.draft
1072 draft = phases.draft
1059
1073
1060 # exclude changesets already public locally and update the others
1074 # exclude changesets already public locally and update the others
1061 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1075 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1062 if pheads:
1076 if pheads:
1063 tr = pullop.gettransaction()
1077 tr = pullop.gettransaction()
1064 phases.advanceboundary(pullop.repo, tr, public, pheads)
1078 phases.advanceboundary(pullop.repo, tr, public, pheads)
1065
1079
1066 # exclude changesets already draft locally and update the others
1080 # exclude changesets already draft locally and update the others
1067 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1081 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1068 if dheads:
1082 if dheads:
1069 tr = pullop.gettransaction()
1083 tr = pullop.gettransaction()
1070 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1084 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1071
1085
1072 def _pullbookmarks(pullop):
1086 def _pullbookmarks(pullop):
1073 """process the remote bookmark information to update the local one"""
1087 """process the remote bookmark information to update the local one"""
1074 if 'bookmarks' in pullop.stepsdone:
1088 if 'bookmarks' in pullop.stepsdone:
1075 return
1089 return
1076 pullop.stepsdone.add('bookmarks')
1090 pullop.stepsdone.add('bookmarks')
1077 repo = pullop.repo
1091 repo = pullop.repo
1078 remotebookmarks = pullop.remotebookmarks
1092 remotebookmarks = pullop.remotebookmarks
1079 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1093 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1080 pullop.remote.url(),
1094 pullop.remote.url(),
1081 pullop.gettransaction,
1095 pullop.gettransaction,
1082 explicit=pullop.explicitbookmarks)
1096 explicit=pullop.explicitbookmarks)
1083
1097
1084 def _pullobsolete(pullop):
1098 def _pullobsolete(pullop):
1085 """utility function to pull obsolete markers from a remote
1099 """utility function to pull obsolete markers from a remote
1086
1100
1087 The `gettransaction` is function that return the pull transaction, creating
1101 The `gettransaction` is function that return the pull transaction, creating
1088 one if necessary. We return the transaction to inform the calling code that
1102 one if necessary. We return the transaction to inform the calling code that
1089 a new transaction have been created (when applicable).
1103 a new transaction have been created (when applicable).
1090
1104
1091 Exists mostly to allow overriding for experimentation purpose"""
1105 Exists mostly to allow overriding for experimentation purpose"""
1092 if 'obsmarkers' in pullop.stepsdone:
1106 if 'obsmarkers' in pullop.stepsdone:
1093 return
1107 return
1094 pullop.stepsdone.add('obsmarkers')
1108 pullop.stepsdone.add('obsmarkers')
1095 tr = None
1109 tr = None
1096 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1110 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1097 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1111 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1098 remoteobs = pullop.remote.listkeys('obsolete')
1112 remoteobs = pullop.remote.listkeys('obsolete')
1099 if 'dump0' in remoteobs:
1113 if 'dump0' in remoteobs:
1100 tr = pullop.gettransaction()
1114 tr = pullop.gettransaction()
1101 for key in sorted(remoteobs, reverse=True):
1115 for key in sorted(remoteobs, reverse=True):
1102 if key.startswith('dump'):
1116 if key.startswith('dump'):
1103 data = base85.b85decode(remoteobs[key])
1117 data = base85.b85decode(remoteobs[key])
1104 pullop.repo.obsstore.mergemarkers(tr, data)
1118 pullop.repo.obsstore.mergemarkers(tr, data)
1105 pullop.repo.invalidatevolatilesets()
1119 pullop.repo.invalidatevolatilesets()
1106 return tr
1120 return tr
1107
1121
1108 def caps20to10(repo):
1122 def caps20to10(repo):
1109 """return a set with appropriate options to use bundle20 during getbundle"""
1123 """return a set with appropriate options to use bundle20 during getbundle"""
1110 caps = set(['HG2Y'])
1124 caps = set(['HG2Y'])
1111 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1125 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1112 caps.add('bundle2=' + urllib.quote(capsblob))
1126 caps.add('bundle2=' + urllib.quote(capsblob))
1113 return caps
1127 return caps
1114
1128
1115 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1129 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1116 getbundle2partsorder = []
1130 getbundle2partsorder = []
1117
1131
1118 # Mapping between step name and function
1132 # Mapping between step name and function
1119 #
1133 #
1120 # This exists to help extensions wrap steps if necessary
1134 # This exists to help extensions wrap steps if necessary
1121 getbundle2partsmapping = {}
1135 getbundle2partsmapping = {}
1122
1136
1123 def getbundle2partsgenerator(stepname):
1137 def getbundle2partsgenerator(stepname):
1124 """decorator for function generating bundle2 part for getbundle
1138 """decorator for function generating bundle2 part for getbundle
1125
1139
1126 The function is added to the step -> function mapping and appended to the
1140 The function is added to the step -> function mapping and appended to the
1127 list of steps. Beware that decorated functions will be added in order
1141 list of steps. Beware that decorated functions will be added in order
1128 (this may matter).
1142 (this may matter).
1129
1143
1130 You can only use this decorator for new steps, if you want to wrap a step
1144 You can only use this decorator for new steps, if you want to wrap a step
1131 from an extension, attack the getbundle2partsmapping dictionary directly."""
1145 from an extension, attack the getbundle2partsmapping dictionary directly."""
1132 def dec(func):
1146 def dec(func):
1133 assert stepname not in getbundle2partsmapping
1147 assert stepname not in getbundle2partsmapping
1134 getbundle2partsmapping[stepname] = func
1148 getbundle2partsmapping[stepname] = func
1135 getbundle2partsorder.append(stepname)
1149 getbundle2partsorder.append(stepname)
1136 return func
1150 return func
1137 return dec
1151 return dec
1138
1152
1139 def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
1153 def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
1140 **kwargs):
1154 **kwargs):
1141 """return a full bundle (with potentially multiple kind of parts)
1155 """return a full bundle (with potentially multiple kind of parts)
1142
1156
1143 Could be a bundle HG10 or a bundle HG2Y depending on bundlecaps
1157 Could be a bundle HG10 or a bundle HG2Y depending on bundlecaps
1144 passed. For now, the bundle can contain only changegroup, but this will
1158 passed. For now, the bundle can contain only changegroup, but this will
1145 changes when more part type will be available for bundle2.
1159 changes when more part type will be available for bundle2.
1146
1160
1147 This is different from changegroup.getchangegroup that only returns an HG10
1161 This is different from changegroup.getchangegroup that only returns an HG10
1148 changegroup bundle. They may eventually get reunited in the future when we
1162 changegroup bundle. They may eventually get reunited in the future when we
1149 have a clearer idea of the API we what to query different data.
1163 have a clearer idea of the API we what to query different data.
1150
1164
1151 The implementation is at a very early stage and will get massive rework
1165 The implementation is at a very early stage and will get massive rework
1152 when the API of bundle is refined.
1166 when the API of bundle is refined.
1153 """
1167 """
1154 # bundle10 case
1168 # bundle10 case
1155 if bundlecaps is None or 'HG2Y' not in bundlecaps:
1169 if bundlecaps is None or 'HG2Y' not in bundlecaps:
1156 if bundlecaps and not kwargs.get('cg', True):
1170 if bundlecaps and not kwargs.get('cg', True):
1157 raise ValueError(_('request for bundle10 must include changegroup'))
1171 raise ValueError(_('request for bundle10 must include changegroup'))
1158
1172
1159 if kwargs:
1173 if kwargs:
1160 raise ValueError(_('unsupported getbundle arguments: %s')
1174 raise ValueError(_('unsupported getbundle arguments: %s')
1161 % ', '.join(sorted(kwargs.keys())))
1175 % ', '.join(sorted(kwargs.keys())))
1162 return changegroup.getchangegroup(repo, source, heads=heads,
1176 return changegroup.getchangegroup(repo, source, heads=heads,
1163 common=common, bundlecaps=bundlecaps)
1177 common=common, bundlecaps=bundlecaps)
1164
1178
1165 # bundle20 case
1179 # bundle20 case
1166 b2caps = {}
1180 b2caps = {}
1167 for bcaps in bundlecaps:
1181 for bcaps in bundlecaps:
1168 if bcaps.startswith('bundle2='):
1182 if bcaps.startswith('bundle2='):
1169 blob = urllib.unquote(bcaps[len('bundle2='):])
1183 blob = urllib.unquote(bcaps[len('bundle2='):])
1170 b2caps.update(bundle2.decodecaps(blob))
1184 b2caps.update(bundle2.decodecaps(blob))
1171 bundler = bundle2.bundle20(repo.ui, b2caps)
1185 bundler = bundle2.bundle20(repo.ui, b2caps)
1172
1186
1173 for name in getbundle2partsorder:
1187 for name in getbundle2partsorder:
1174 func = getbundle2partsmapping[name]
1188 func = getbundle2partsmapping[name]
1175 kwargs['heads'] = heads
1189 kwargs['heads'] = heads
1176 kwargs['common'] = common
1190 kwargs['common'] = common
1177 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1191 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1178 **kwargs)
1192 **kwargs)
1179
1193
1180 return util.chunkbuffer(bundler.getchunks())
1194 return util.chunkbuffer(bundler.getchunks())
1181
1195
1182 @getbundle2partsgenerator('changegroup')
1196 @getbundle2partsgenerator('changegroup')
1183 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1197 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1184 b2caps=None, heads=None, common=None, **kwargs):
1198 b2caps=None, heads=None, common=None, **kwargs):
1185 """add a changegroup part to the requested bundle"""
1199 """add a changegroup part to the requested bundle"""
1186 cg = None
1200 cg = None
1187 if kwargs.get('cg', True):
1201 if kwargs.get('cg', True):
1188 # build changegroup bundle here.
1202 # build changegroup bundle here.
1189 version = None
1203 version = None
1190 cgversions = b2caps.get('b2x:changegroup')
1204 cgversions = b2caps.get('b2x:changegroup')
1191 if cgversions is None:
1205 if cgversions is None:
1192 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1206 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1193 common=common,
1207 common=common,
1194 bundlecaps=bundlecaps)
1208 bundlecaps=bundlecaps)
1195 else:
1209 else:
1196 cgversions = [v for v in cgversions if v in changegroup.packermap]
1210 cgversions = [v for v in cgversions if v in changegroup.packermap]
1197 if not cgversions:
1211 if not cgversions:
1198 raise ValueError(_('no common changegroup version'))
1212 raise ValueError(_('no common changegroup version'))
1199 version = max(cgversions)
1213 version = max(cgversions)
1200 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1214 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1201 common=common,
1215 common=common,
1202 bundlecaps=bundlecaps,
1216 bundlecaps=bundlecaps,
1203 version=version)
1217 version=version)
1204
1218
1205 if cg:
1219 if cg:
1206 part = bundler.newpart('b2x:changegroup', data=cg)
1220 part = bundler.newpart('b2x:changegroup', data=cg)
1207 if version is not None:
1221 if version is not None:
1208 part.addparam('version', version)
1222 part.addparam('version', version)
1209
1223
1210 @getbundle2partsgenerator('listkeys')
1224 @getbundle2partsgenerator('listkeys')
1211 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1225 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1212 b2caps=None, **kwargs):
1226 b2caps=None, **kwargs):
1213 """add parts containing listkeys namespaces to the requested bundle"""
1227 """add parts containing listkeys namespaces to the requested bundle"""
1214 listkeys = kwargs.get('listkeys', ())
1228 listkeys = kwargs.get('listkeys', ())
1215 for namespace in listkeys:
1229 for namespace in listkeys:
1216 part = bundler.newpart('b2x:listkeys')
1230 part = bundler.newpart('b2x:listkeys')
1217 part.addparam('namespace', namespace)
1231 part.addparam('namespace', namespace)
1218 keys = repo.listkeys(namespace).items()
1232 keys = repo.listkeys(namespace).items()
1219 part.data = pushkey.encodekeys(keys)
1233 part.data = pushkey.encodekeys(keys)
1220
1234
1221 @getbundle2partsgenerator('obsmarkers')
1235 @getbundle2partsgenerator('obsmarkers')
1222 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1236 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1223 b2caps=None, heads=None, **kwargs):
1237 b2caps=None, heads=None, **kwargs):
1224 """add an obsolescence markers part to the requested bundle"""
1238 """add an obsolescence markers part to the requested bundle"""
1225 if kwargs.get('obsmarkers', False):
1239 if kwargs.get('obsmarkers', False):
1226 if heads is None:
1240 if heads is None:
1227 heads = repo.heads()
1241 heads = repo.heads()
1228 subset = [c.node() for c in repo.set('::%ln', heads)]
1242 subset = [c.node() for c in repo.set('::%ln', heads)]
1229 markers = repo.obsstore.relevantmarkers(subset)
1243 markers = repo.obsstore.relevantmarkers(subset)
1230 buildobsmarkerspart(bundler, markers)
1244 buildobsmarkerspart(bundler, markers)
1231
1245
1232 def check_heads(repo, their_heads, context):
1246 def check_heads(repo, their_heads, context):
1233 """check if the heads of a repo have been modified
1247 """check if the heads of a repo have been modified
1234
1248
1235 Used by peer for unbundling.
1249 Used by peer for unbundling.
1236 """
1250 """
1237 heads = repo.heads()
1251 heads = repo.heads()
1238 heads_hash = util.sha1(''.join(sorted(heads))).digest()
1252 heads_hash = util.sha1(''.join(sorted(heads))).digest()
1239 if not (their_heads == ['force'] or their_heads == heads or
1253 if not (their_heads == ['force'] or their_heads == heads or
1240 their_heads == ['hashed', heads_hash]):
1254 their_heads == ['hashed', heads_hash]):
1241 # someone else committed/pushed/unbundled while we
1255 # someone else committed/pushed/unbundled while we
1242 # were transferring data
1256 # were transferring data
1243 raise error.PushRaced('repository changed while %s - '
1257 raise error.PushRaced('repository changed while %s - '
1244 'please try again' % context)
1258 'please try again' % context)
1245
1259
1246 def unbundle(repo, cg, heads, source, url):
1260 def unbundle(repo, cg, heads, source, url):
1247 """Apply a bundle to a repo.
1261 """Apply a bundle to a repo.
1248
1262
1249 this function makes sure the repo is locked during the application and have
1263 this function makes sure the repo is locked during the application and have
1250 mechanism to check that no push race occurred between the creation of the
1264 mechanism to check that no push race occurred between the creation of the
1251 bundle and its application.
1265 bundle and its application.
1252
1266
1253 If the push was raced as PushRaced exception is raised."""
1267 If the push was raced as PushRaced exception is raised."""
1254 r = 0
1268 r = 0
1255 # need a transaction when processing a bundle2 stream
1269 # need a transaction when processing a bundle2 stream
1256 tr = None
1270 tr = None
1257 lock = repo.lock()
1271 lock = repo.lock()
1258 try:
1272 try:
1259 check_heads(repo, heads, 'uploading changes')
1273 check_heads(repo, heads, 'uploading changes')
1260 # push can proceed
1274 # push can proceed
1261 if util.safehasattr(cg, 'params'):
1275 if util.safehasattr(cg, 'params'):
1262 try:
1276 try:
1263 tr = repo.transaction('unbundle')
1277 tr = repo.transaction('unbundle')
1264 tr.hookargs['source'] = source
1278 tr.hookargs['source'] = source
1265 tr.hookargs['url'] = url
1279 tr.hookargs['url'] = url
1266 tr.hookargs['bundle2-exp'] = '1'
1280 tr.hookargs['bundle2-exp'] = '1'
1267 r = bundle2.processbundle(repo, cg, lambda: tr).reply
1281 r = bundle2.processbundle(repo, cg, lambda: tr).reply
1268 cl = repo.unfiltered().changelog
1282 cl = repo.unfiltered().changelog
1269 p = cl.writepending() and repo.root or ""
1283 p = cl.writepending() and repo.root or ""
1270 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
1284 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
1271 **tr.hookargs)
1285 **tr.hookargs)
1272 tr.close()
1286 tr.close()
1273 hookargs = dict(tr.hookargs)
1287 hookargs = dict(tr.hookargs)
1274 def runhooks():
1288 def runhooks():
1275 repo.hook('b2x-transactionclose', **hookargs)
1289 repo.hook('b2x-transactionclose', **hookargs)
1276 repo._afterlock(runhooks)
1290 repo._afterlock(runhooks)
1277 except Exception, exc:
1291 except Exception, exc:
1278 exc.duringunbundle2 = True
1292 exc.duringunbundle2 = True
1279 raise
1293 raise
1280 else:
1294 else:
1281 r = changegroup.addchangegroup(repo, cg, source, url)
1295 r = changegroup.addchangegroup(repo, cg, source, url)
1282 finally:
1296 finally:
1283 if tr is not None:
1297 if tr is not None:
1284 tr.release()
1298 tr.release()
1285 lock.release()
1299 lock.release()
1286 return r
1300 return r
General Comments 0
You need to be logged in to leave comments. Login now