##// END OF EJS Templates
getbundle: send highest changegroup format supported by both side...
Pierre-Yves David -
r23179:6bb9533f default
parent child Browse files
Show More
@@ -1,1271 +1,1286 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 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
449 pushop.outgoing)
449 pushop.outgoing)
450 cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg)
450 cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg)
451 def handlereply(op):
451 def handlereply(op):
452 """extract addchangegroup returns from server reply"""
452 """extract addchangegroup returns from server reply"""
453 cgreplies = op.records.getreplies(cgpart.id)
453 cgreplies = op.records.getreplies(cgpart.id)
454 assert len(cgreplies['changegroup']) == 1
454 assert len(cgreplies['changegroup']) == 1
455 pushop.cgresult = cgreplies['changegroup'][0]['return']
455 pushop.cgresult = cgreplies['changegroup'][0]['return']
456 return handlereply
456 return handlereply
457
457
458 @b2partsgenerator('phase')
458 @b2partsgenerator('phase')
459 def _pushb2phases(pushop, bundler):
459 def _pushb2phases(pushop, bundler):
460 """handle phase push through bundle2"""
460 """handle phase push through bundle2"""
461 if 'phases' in pushop.stepsdone:
461 if 'phases' in pushop.stepsdone:
462 return
462 return
463 b2caps = bundle2.bundle2caps(pushop.remote)
463 b2caps = bundle2.bundle2caps(pushop.remote)
464 if not 'b2x:pushkey' in b2caps:
464 if not 'b2x:pushkey' in b2caps:
465 return
465 return
466 pushop.stepsdone.add('phases')
466 pushop.stepsdone.add('phases')
467 part2node = []
467 part2node = []
468 enc = pushkey.encode
468 enc = pushkey.encode
469 for newremotehead in pushop.outdatedphases:
469 for newremotehead in pushop.outdatedphases:
470 part = bundler.newpart('b2x:pushkey')
470 part = bundler.newpart('b2x:pushkey')
471 part.addparam('namespace', enc('phases'))
471 part.addparam('namespace', enc('phases'))
472 part.addparam('key', enc(newremotehead.hex()))
472 part.addparam('key', enc(newremotehead.hex()))
473 part.addparam('old', enc(str(phases.draft)))
473 part.addparam('old', enc(str(phases.draft)))
474 part.addparam('new', enc(str(phases.public)))
474 part.addparam('new', enc(str(phases.public)))
475 part2node.append((part.id, newremotehead))
475 part2node.append((part.id, newremotehead))
476 def handlereply(op):
476 def handlereply(op):
477 for partid, node in part2node:
477 for partid, node in part2node:
478 partrep = op.records.getreplies(partid)
478 partrep = op.records.getreplies(partid)
479 results = partrep['pushkey']
479 results = partrep['pushkey']
480 assert len(results) <= 1
480 assert len(results) <= 1
481 msg = None
481 msg = None
482 if not results:
482 if not results:
483 msg = _('server ignored update of %s to public!\n') % node
483 msg = _('server ignored update of %s to public!\n') % node
484 elif not int(results[0]['return']):
484 elif not int(results[0]['return']):
485 msg = _('updating %s to public failed!\n') % node
485 msg = _('updating %s to public failed!\n') % node
486 if msg is not None:
486 if msg is not None:
487 pushop.ui.warn(msg)
487 pushop.ui.warn(msg)
488 return handlereply
488 return handlereply
489
489
490 @b2partsgenerator('obsmarkers')
490 @b2partsgenerator('obsmarkers')
491 def _pushb2obsmarkers(pushop, bundler):
491 def _pushb2obsmarkers(pushop, bundler):
492 if 'obsmarkers' in pushop.stepsdone:
492 if 'obsmarkers' in pushop.stepsdone:
493 return
493 return
494 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
494 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
495 if obsolete.commonversion(remoteversions) is None:
495 if obsolete.commonversion(remoteversions) is None:
496 return
496 return
497 pushop.stepsdone.add('obsmarkers')
497 pushop.stepsdone.add('obsmarkers')
498 if pushop.outobsmarkers:
498 if pushop.outobsmarkers:
499 buildobsmarkerspart(bundler, pushop.outobsmarkers)
499 buildobsmarkerspart(bundler, pushop.outobsmarkers)
500
500
501 @b2partsgenerator('bookmarks')
501 @b2partsgenerator('bookmarks')
502 def _pushb2bookmarks(pushop, bundler):
502 def _pushb2bookmarks(pushop, bundler):
503 """handle phase push through bundle2"""
503 """handle phase push through bundle2"""
504 if 'bookmarks' in pushop.stepsdone:
504 if 'bookmarks' in pushop.stepsdone:
505 return
505 return
506 b2caps = bundle2.bundle2caps(pushop.remote)
506 b2caps = bundle2.bundle2caps(pushop.remote)
507 if 'b2x:pushkey' not in b2caps:
507 if 'b2x:pushkey' not in b2caps:
508 return
508 return
509 pushop.stepsdone.add('bookmarks')
509 pushop.stepsdone.add('bookmarks')
510 part2book = []
510 part2book = []
511 enc = pushkey.encode
511 enc = pushkey.encode
512 for book, old, new in pushop.outbookmarks:
512 for book, old, new in pushop.outbookmarks:
513 part = bundler.newpart('b2x:pushkey')
513 part = bundler.newpart('b2x:pushkey')
514 part.addparam('namespace', enc('bookmarks'))
514 part.addparam('namespace', enc('bookmarks'))
515 part.addparam('key', enc(book))
515 part.addparam('key', enc(book))
516 part.addparam('old', enc(old))
516 part.addparam('old', enc(old))
517 part.addparam('new', enc(new))
517 part.addparam('new', enc(new))
518 action = 'update'
518 action = 'update'
519 if not old:
519 if not old:
520 action = 'export'
520 action = 'export'
521 elif not new:
521 elif not new:
522 action = 'delete'
522 action = 'delete'
523 part2book.append((part.id, book, action))
523 part2book.append((part.id, book, action))
524
524
525
525
526 def handlereply(op):
526 def handlereply(op):
527 ui = pushop.ui
527 ui = pushop.ui
528 for partid, book, action in part2book:
528 for partid, book, action in part2book:
529 partrep = op.records.getreplies(partid)
529 partrep = op.records.getreplies(partid)
530 results = partrep['pushkey']
530 results = partrep['pushkey']
531 assert len(results) <= 1
531 assert len(results) <= 1
532 if not results:
532 if not results:
533 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
533 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
534 else:
534 else:
535 ret = int(results[0]['return'])
535 ret = int(results[0]['return'])
536 if ret:
536 if ret:
537 ui.status(bookmsgmap[action][0] % book)
537 ui.status(bookmsgmap[action][0] % book)
538 else:
538 else:
539 ui.warn(bookmsgmap[action][1] % book)
539 ui.warn(bookmsgmap[action][1] % book)
540 if pushop.bkresult is not None:
540 if pushop.bkresult is not None:
541 pushop.bkresult = 1
541 pushop.bkresult = 1
542 return handlereply
542 return handlereply
543
543
544
544
545 def _pushbundle2(pushop):
545 def _pushbundle2(pushop):
546 """push data to the remote using bundle2
546 """push data to the remote using bundle2
547
547
548 The only currently supported type of data is changegroup but this will
548 The only currently supported type of data is changegroup but this will
549 evolve in the future."""
549 evolve in the future."""
550 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
550 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
551 # create reply capability
551 # create reply capability
552 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
552 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
553 bundler.newpart('b2x:replycaps', data=capsblob)
553 bundler.newpart('b2x:replycaps', data=capsblob)
554 replyhandlers = []
554 replyhandlers = []
555 for partgenname in b2partsgenorder:
555 for partgenname in b2partsgenorder:
556 partgen = b2partsgenmapping[partgenname]
556 partgen = b2partsgenmapping[partgenname]
557 ret = partgen(pushop, bundler)
557 ret = partgen(pushop, bundler)
558 if callable(ret):
558 if callable(ret):
559 replyhandlers.append(ret)
559 replyhandlers.append(ret)
560 # do not push if nothing to push
560 # do not push if nothing to push
561 if bundler.nbparts <= 1:
561 if bundler.nbparts <= 1:
562 return
562 return
563 stream = util.chunkbuffer(bundler.getchunks())
563 stream = util.chunkbuffer(bundler.getchunks())
564 try:
564 try:
565 reply = pushop.remote.unbundle(stream, ['force'], 'push')
565 reply = pushop.remote.unbundle(stream, ['force'], 'push')
566 except error.BundleValueError, exc:
566 except error.BundleValueError, exc:
567 raise util.Abort('missing support for %s' % exc)
567 raise util.Abort('missing support for %s' % exc)
568 try:
568 try:
569 op = bundle2.processbundle(pushop.repo, reply)
569 op = bundle2.processbundle(pushop.repo, reply)
570 except error.BundleValueError, exc:
570 except error.BundleValueError, exc:
571 raise util.Abort('missing support for %s' % exc)
571 raise util.Abort('missing support for %s' % exc)
572 for rephand in replyhandlers:
572 for rephand in replyhandlers:
573 rephand(op)
573 rephand(op)
574
574
575 def _pushchangeset(pushop):
575 def _pushchangeset(pushop):
576 """Make the actual push of changeset bundle to remote repo"""
576 """Make the actual push of changeset bundle to remote repo"""
577 if 'changesets' in pushop.stepsdone:
577 if 'changesets' in pushop.stepsdone:
578 return
578 return
579 pushop.stepsdone.add('changesets')
579 pushop.stepsdone.add('changesets')
580 if not _pushcheckoutgoing(pushop):
580 if not _pushcheckoutgoing(pushop):
581 return
581 return
582 pushop.repo.prepushoutgoinghooks(pushop.repo,
582 pushop.repo.prepushoutgoinghooks(pushop.repo,
583 pushop.remote,
583 pushop.remote,
584 pushop.outgoing)
584 pushop.outgoing)
585 outgoing = pushop.outgoing
585 outgoing = pushop.outgoing
586 unbundle = pushop.remote.capable('unbundle')
586 unbundle = pushop.remote.capable('unbundle')
587 # TODO: get bundlecaps from remote
587 # TODO: get bundlecaps from remote
588 bundlecaps = None
588 bundlecaps = None
589 # create a changegroup from local
589 # create a changegroup from local
590 if pushop.revs is None and not (outgoing.excluded
590 if pushop.revs is None and not (outgoing.excluded
591 or pushop.repo.changelog.filteredrevs):
591 or pushop.repo.changelog.filteredrevs):
592 # push everything,
592 # push everything,
593 # use the fast path, no race possible on push
593 # use the fast path, no race possible on push
594 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
594 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
595 cg = changegroup.getsubset(pushop.repo,
595 cg = changegroup.getsubset(pushop.repo,
596 outgoing,
596 outgoing,
597 bundler,
597 bundler,
598 'push',
598 'push',
599 fastpath=True)
599 fastpath=True)
600 else:
600 else:
601 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
601 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
602 bundlecaps)
602 bundlecaps)
603
603
604 # apply changegroup to remote
604 # apply changegroup to remote
605 if unbundle:
605 if unbundle:
606 # local repo finds heads on server, finds out what
606 # local repo finds heads on server, finds out what
607 # revs it must push. once revs transferred, if server
607 # revs it must push. once revs transferred, if server
608 # finds it has different heads (someone else won
608 # finds it has different heads (someone else won
609 # commit/push race), server aborts.
609 # commit/push race), server aborts.
610 if pushop.force:
610 if pushop.force:
611 remoteheads = ['force']
611 remoteheads = ['force']
612 else:
612 else:
613 remoteheads = pushop.remoteheads
613 remoteheads = pushop.remoteheads
614 # ssh: return remote's addchangegroup()
614 # ssh: return remote's addchangegroup()
615 # http: return remote's addchangegroup() or 0 for error
615 # http: return remote's addchangegroup() or 0 for error
616 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
616 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
617 pushop.repo.url())
617 pushop.repo.url())
618 else:
618 else:
619 # we return an integer indicating remote head count
619 # we return an integer indicating remote head count
620 # change
620 # change
621 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
621 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
622 pushop.repo.url())
622 pushop.repo.url())
623
623
624 def _pushsyncphase(pushop):
624 def _pushsyncphase(pushop):
625 """synchronise phase information locally and remotely"""
625 """synchronise phase information locally and remotely"""
626 cheads = pushop.commonheads
626 cheads = pushop.commonheads
627 # even when we don't push, exchanging phase data is useful
627 # even when we don't push, exchanging phase data is useful
628 remotephases = pushop.remote.listkeys('phases')
628 remotephases = pushop.remote.listkeys('phases')
629 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
629 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
630 and remotephases # server supports phases
630 and remotephases # server supports phases
631 and pushop.cgresult is None # nothing was pushed
631 and pushop.cgresult is None # nothing was pushed
632 and remotephases.get('publishing', False)):
632 and remotephases.get('publishing', False)):
633 # When:
633 # When:
634 # - this is a subrepo push
634 # - this is a subrepo push
635 # - and remote support phase
635 # - and remote support phase
636 # - and no changeset was pushed
636 # - and no changeset was pushed
637 # - and remote is publishing
637 # - and remote is publishing
638 # We may be in issue 3871 case!
638 # We may be in issue 3871 case!
639 # We drop the possible phase synchronisation done by
639 # We drop the possible phase synchronisation done by
640 # courtesy to publish changesets possibly locally draft
640 # courtesy to publish changesets possibly locally draft
641 # on the remote.
641 # on the remote.
642 remotephases = {'publishing': 'True'}
642 remotephases = {'publishing': 'True'}
643 if not remotephases: # old server or public only reply from non-publishing
643 if not remotephases: # old server or public only reply from non-publishing
644 _localphasemove(pushop, cheads)
644 _localphasemove(pushop, cheads)
645 # don't push any phase data as there is nothing to push
645 # don't push any phase data as there is nothing to push
646 else:
646 else:
647 ana = phases.analyzeremotephases(pushop.repo, cheads,
647 ana = phases.analyzeremotephases(pushop.repo, cheads,
648 remotephases)
648 remotephases)
649 pheads, droots = ana
649 pheads, droots = ana
650 ### Apply remote phase on local
650 ### Apply remote phase on local
651 if remotephases.get('publishing', False):
651 if remotephases.get('publishing', False):
652 _localphasemove(pushop, cheads)
652 _localphasemove(pushop, cheads)
653 else: # publish = False
653 else: # publish = False
654 _localphasemove(pushop, pheads)
654 _localphasemove(pushop, pheads)
655 _localphasemove(pushop, cheads, phases.draft)
655 _localphasemove(pushop, cheads, phases.draft)
656 ### Apply local phase on remote
656 ### Apply local phase on remote
657
657
658 if pushop.cgresult:
658 if pushop.cgresult:
659 if 'phases' in pushop.stepsdone:
659 if 'phases' in pushop.stepsdone:
660 # phases already pushed though bundle2
660 # phases already pushed though bundle2
661 return
661 return
662 outdated = pushop.outdatedphases
662 outdated = pushop.outdatedphases
663 else:
663 else:
664 outdated = pushop.fallbackoutdatedphases
664 outdated = pushop.fallbackoutdatedphases
665
665
666 pushop.stepsdone.add('phases')
666 pushop.stepsdone.add('phases')
667
667
668 # filter heads already turned public by the push
668 # filter heads already turned public by the push
669 outdated = [c for c in outdated if c.node() not in pheads]
669 outdated = [c for c in outdated if c.node() not in pheads]
670 b2caps = bundle2.bundle2caps(pushop.remote)
670 b2caps = bundle2.bundle2caps(pushop.remote)
671 if 'b2x:pushkey' in b2caps:
671 if 'b2x:pushkey' in b2caps:
672 # server supports bundle2, let's do a batched push through it
672 # server supports bundle2, let's do a batched push through it
673 #
673 #
674 # This will eventually be unified with the changesets bundle2 push
674 # This will eventually be unified with the changesets bundle2 push
675 bundler = bundle2.bundle20(pushop.ui, b2caps)
675 bundler = bundle2.bundle20(pushop.ui, b2caps)
676 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
676 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
677 bundler.newpart('b2x:replycaps', data=capsblob)
677 bundler.newpart('b2x:replycaps', data=capsblob)
678 part2node = []
678 part2node = []
679 enc = pushkey.encode
679 enc = pushkey.encode
680 for newremotehead in outdated:
680 for newremotehead in outdated:
681 part = bundler.newpart('b2x:pushkey')
681 part = bundler.newpart('b2x:pushkey')
682 part.addparam('namespace', enc('phases'))
682 part.addparam('namespace', enc('phases'))
683 part.addparam('key', enc(newremotehead.hex()))
683 part.addparam('key', enc(newremotehead.hex()))
684 part.addparam('old', enc(str(phases.draft)))
684 part.addparam('old', enc(str(phases.draft)))
685 part.addparam('new', enc(str(phases.public)))
685 part.addparam('new', enc(str(phases.public)))
686 part2node.append((part.id, newremotehead))
686 part2node.append((part.id, newremotehead))
687 stream = util.chunkbuffer(bundler.getchunks())
687 stream = util.chunkbuffer(bundler.getchunks())
688 try:
688 try:
689 reply = pushop.remote.unbundle(stream, ['force'], 'push')
689 reply = pushop.remote.unbundle(stream, ['force'], 'push')
690 op = bundle2.processbundle(pushop.repo, reply)
690 op = bundle2.processbundle(pushop.repo, reply)
691 except error.BundleValueError, exc:
691 except error.BundleValueError, exc:
692 raise util.Abort('missing support for %s' % exc)
692 raise util.Abort('missing support for %s' % exc)
693 for partid, node in part2node:
693 for partid, node in part2node:
694 partrep = op.records.getreplies(partid)
694 partrep = op.records.getreplies(partid)
695 results = partrep['pushkey']
695 results = partrep['pushkey']
696 assert len(results) <= 1
696 assert len(results) <= 1
697 msg = None
697 msg = None
698 if not results:
698 if not results:
699 msg = _('server ignored update of %s to public!\n') % node
699 msg = _('server ignored update of %s to public!\n') % node
700 elif not int(results[0]['return']):
700 elif not int(results[0]['return']):
701 msg = _('updating %s to public failed!\n') % node
701 msg = _('updating %s to public failed!\n') % node
702 if msg is not None:
702 if msg is not None:
703 pushop.ui.warn(msg)
703 pushop.ui.warn(msg)
704
704
705 else:
705 else:
706 # fallback to independent pushkey command
706 # fallback to independent pushkey command
707 for newremotehead in outdated:
707 for newremotehead in outdated:
708 r = pushop.remote.pushkey('phases',
708 r = pushop.remote.pushkey('phases',
709 newremotehead.hex(),
709 newremotehead.hex(),
710 str(phases.draft),
710 str(phases.draft),
711 str(phases.public))
711 str(phases.public))
712 if not r:
712 if not r:
713 pushop.ui.warn(_('updating %s to public failed!\n')
713 pushop.ui.warn(_('updating %s to public failed!\n')
714 % newremotehead)
714 % newremotehead)
715
715
716 def _localphasemove(pushop, nodes, phase=phases.public):
716 def _localphasemove(pushop, nodes, phase=phases.public):
717 """move <nodes> to <phase> in the local source repo"""
717 """move <nodes> to <phase> in the local source repo"""
718 if pushop.locallocked:
718 if pushop.locallocked:
719 tr = pushop.repo.transaction('push-phase-sync')
719 tr = pushop.repo.transaction('push-phase-sync')
720 try:
720 try:
721 phases.advanceboundary(pushop.repo, tr, phase, nodes)
721 phases.advanceboundary(pushop.repo, tr, phase, nodes)
722 tr.close()
722 tr.close()
723 finally:
723 finally:
724 tr.release()
724 tr.release()
725 else:
725 else:
726 # repo is not locked, do not change any phases!
726 # repo is not locked, do not change any phases!
727 # Informs the user that phases should have been moved when
727 # Informs the user that phases should have been moved when
728 # applicable.
728 # applicable.
729 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
729 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
730 phasestr = phases.phasenames[phase]
730 phasestr = phases.phasenames[phase]
731 if actualmoves:
731 if actualmoves:
732 pushop.ui.status(_('cannot lock source repo, skipping '
732 pushop.ui.status(_('cannot lock source repo, skipping '
733 'local %s phase update\n') % phasestr)
733 'local %s phase update\n') % phasestr)
734
734
735 def _pushobsolete(pushop):
735 def _pushobsolete(pushop):
736 """utility function to push obsolete markers to a remote"""
736 """utility function to push obsolete markers to a remote"""
737 if 'obsmarkers' in pushop.stepsdone:
737 if 'obsmarkers' in pushop.stepsdone:
738 return
738 return
739 pushop.ui.debug('try to push obsolete markers to remote\n')
739 pushop.ui.debug('try to push obsolete markers to remote\n')
740 repo = pushop.repo
740 repo = pushop.repo
741 remote = pushop.remote
741 remote = pushop.remote
742 pushop.stepsdone.add('obsmarkers')
742 pushop.stepsdone.add('obsmarkers')
743 if pushop.outobsmarkers:
743 if pushop.outobsmarkers:
744 rslts = []
744 rslts = []
745 remotedata = obsolete._pushkeyescape(pushop.outobsmarkers)
745 remotedata = obsolete._pushkeyescape(pushop.outobsmarkers)
746 for key in sorted(remotedata, reverse=True):
746 for key in sorted(remotedata, reverse=True):
747 # reverse sort to ensure we end with dump0
747 # reverse sort to ensure we end with dump0
748 data = remotedata[key]
748 data = remotedata[key]
749 rslts.append(remote.pushkey('obsolete', key, '', data))
749 rslts.append(remote.pushkey('obsolete', key, '', data))
750 if [r for r in rslts if not r]:
750 if [r for r in rslts if not r]:
751 msg = _('failed to push some obsolete markers!\n')
751 msg = _('failed to push some obsolete markers!\n')
752 repo.ui.warn(msg)
752 repo.ui.warn(msg)
753
753
754 def _pushbookmark(pushop):
754 def _pushbookmark(pushop):
755 """Update bookmark position on remote"""
755 """Update bookmark position on remote"""
756 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
756 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
757 return
757 return
758 pushop.stepsdone.add('bookmarks')
758 pushop.stepsdone.add('bookmarks')
759 ui = pushop.ui
759 ui = pushop.ui
760 remote = pushop.remote
760 remote = pushop.remote
761
761
762 for b, old, new in pushop.outbookmarks:
762 for b, old, new in pushop.outbookmarks:
763 action = 'update'
763 action = 'update'
764 if not old:
764 if not old:
765 action = 'export'
765 action = 'export'
766 elif not new:
766 elif not new:
767 action = 'delete'
767 action = 'delete'
768 if remote.pushkey('bookmarks', b, old, new):
768 if remote.pushkey('bookmarks', b, old, new):
769 ui.status(bookmsgmap[action][0] % b)
769 ui.status(bookmsgmap[action][0] % b)
770 else:
770 else:
771 ui.warn(bookmsgmap[action][1] % b)
771 ui.warn(bookmsgmap[action][1] % b)
772 # discovery can have set the value form invalid entry
772 # discovery can have set the value form invalid entry
773 if pushop.bkresult is not None:
773 if pushop.bkresult is not None:
774 pushop.bkresult = 1
774 pushop.bkresult = 1
775
775
776 class pulloperation(object):
776 class pulloperation(object):
777 """A object that represent a single pull operation
777 """A object that represent a single pull operation
778
778
779 It purpose is to carry push related state and very common operation.
779 It purpose is to carry push related state and very common operation.
780
780
781 A new should be created at the beginning of each pull and discarded
781 A new should be created at the beginning of each pull and discarded
782 afterward.
782 afterward.
783 """
783 """
784
784
785 def __init__(self, repo, remote, heads=None, force=False, bookmarks=()):
785 def __init__(self, repo, remote, heads=None, force=False, bookmarks=()):
786 # repo we pull into
786 # repo we pull into
787 self.repo = repo
787 self.repo = repo
788 # repo we pull from
788 # repo we pull from
789 self.remote = remote
789 self.remote = remote
790 # revision we try to pull (None is "all")
790 # revision we try to pull (None is "all")
791 self.heads = heads
791 self.heads = heads
792 # bookmark pulled explicitly
792 # bookmark pulled explicitly
793 self.explicitbookmarks = bookmarks
793 self.explicitbookmarks = bookmarks
794 # do we force pull?
794 # do we force pull?
795 self.force = force
795 self.force = force
796 # the name the pull transaction
796 # the name the pull transaction
797 self._trname = 'pull\n' + util.hidepassword(remote.url())
797 self._trname = 'pull\n' + util.hidepassword(remote.url())
798 # hold the transaction once created
798 # hold the transaction once created
799 self._tr = None
799 self._tr = None
800 # set of common changeset between local and remote before pull
800 # set of common changeset between local and remote before pull
801 self.common = None
801 self.common = None
802 # set of pulled head
802 # set of pulled head
803 self.rheads = None
803 self.rheads = None
804 # list of missing changeset to fetch remotely
804 # list of missing changeset to fetch remotely
805 self.fetch = None
805 self.fetch = None
806 # remote bookmarks data
806 # remote bookmarks data
807 self.remotebookmarks = None
807 self.remotebookmarks = None
808 # result of changegroup pulling (used as return code by pull)
808 # result of changegroup pulling (used as return code by pull)
809 self.cgresult = None
809 self.cgresult = None
810 # list of step already done
810 # list of step already done
811 self.stepsdone = set()
811 self.stepsdone = set()
812
812
813 @util.propertycache
813 @util.propertycache
814 def pulledsubset(self):
814 def pulledsubset(self):
815 """heads of the set of changeset target by the pull"""
815 """heads of the set of changeset target by the pull"""
816 # compute target subset
816 # compute target subset
817 if self.heads is None:
817 if self.heads is None:
818 # We pulled every thing possible
818 # We pulled every thing possible
819 # sync on everything common
819 # sync on everything common
820 c = set(self.common)
820 c = set(self.common)
821 ret = list(self.common)
821 ret = list(self.common)
822 for n in self.rheads:
822 for n in self.rheads:
823 if n not in c:
823 if n not in c:
824 ret.append(n)
824 ret.append(n)
825 return ret
825 return ret
826 else:
826 else:
827 # We pulled a specific subset
827 # We pulled a specific subset
828 # sync on this subset
828 # sync on this subset
829 return self.heads
829 return self.heads
830
830
831 def gettransaction(self):
831 def gettransaction(self):
832 """get appropriate pull transaction, creating it if needed"""
832 """get appropriate pull transaction, creating it if needed"""
833 if self._tr is None:
833 if self._tr is None:
834 self._tr = self.repo.transaction(self._trname)
834 self._tr = self.repo.transaction(self._trname)
835 self._tr.hookargs['source'] = 'pull'
835 self._tr.hookargs['source'] = 'pull'
836 self._tr.hookargs['url'] = self.remote.url()
836 self._tr.hookargs['url'] = self.remote.url()
837 return self._tr
837 return self._tr
838
838
839 def closetransaction(self):
839 def closetransaction(self):
840 """close transaction if created"""
840 """close transaction if created"""
841 if self._tr is not None:
841 if self._tr is not None:
842 repo = self.repo
842 repo = self.repo
843 cl = repo.unfiltered().changelog
843 cl = repo.unfiltered().changelog
844 p = cl.writepending() and repo.root or ""
844 p = cl.writepending() and repo.root or ""
845 p = cl.writepending() and repo.root or ""
845 p = cl.writepending() and repo.root or ""
846 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
846 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
847 **self._tr.hookargs)
847 **self._tr.hookargs)
848 self._tr.close()
848 self._tr.close()
849 hookargs = dict(self._tr.hookargs)
849 hookargs = dict(self._tr.hookargs)
850 def runhooks():
850 def runhooks():
851 repo.hook('b2x-transactionclose', **hookargs)
851 repo.hook('b2x-transactionclose', **hookargs)
852 repo._afterlock(runhooks)
852 repo._afterlock(runhooks)
853
853
854 def releasetransaction(self):
854 def releasetransaction(self):
855 """release transaction if created"""
855 """release transaction if created"""
856 if self._tr is not None:
856 if self._tr is not None:
857 self._tr.release()
857 self._tr.release()
858
858
859 def pull(repo, remote, heads=None, force=False, bookmarks=()):
859 def pull(repo, remote, heads=None, force=False, bookmarks=()):
860 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks)
860 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks)
861 if pullop.remote.local():
861 if pullop.remote.local():
862 missing = set(pullop.remote.requirements) - pullop.repo.supported
862 missing = set(pullop.remote.requirements) - pullop.repo.supported
863 if missing:
863 if missing:
864 msg = _("required features are not"
864 msg = _("required features are not"
865 " supported in the destination:"
865 " supported in the destination:"
866 " %s") % (', '.join(sorted(missing)))
866 " %s") % (', '.join(sorted(missing)))
867 raise util.Abort(msg)
867 raise util.Abort(msg)
868
868
869 pullop.remotebookmarks = remote.listkeys('bookmarks')
869 pullop.remotebookmarks = remote.listkeys('bookmarks')
870 lock = pullop.repo.lock()
870 lock = pullop.repo.lock()
871 try:
871 try:
872 _pulldiscovery(pullop)
872 _pulldiscovery(pullop)
873 if (pullop.repo.ui.configbool('experimental', 'bundle2-exp', False)
873 if (pullop.repo.ui.configbool('experimental', 'bundle2-exp', False)
874 and pullop.remote.capable('bundle2-exp')):
874 and pullop.remote.capable('bundle2-exp')):
875 _pullbundle2(pullop)
875 _pullbundle2(pullop)
876 _pullchangeset(pullop)
876 _pullchangeset(pullop)
877 _pullphase(pullop)
877 _pullphase(pullop)
878 _pullbookmarks(pullop)
878 _pullbookmarks(pullop)
879 _pullobsolete(pullop)
879 _pullobsolete(pullop)
880 pullop.closetransaction()
880 pullop.closetransaction()
881 finally:
881 finally:
882 pullop.releasetransaction()
882 pullop.releasetransaction()
883 lock.release()
883 lock.release()
884
884
885 return pullop
885 return pullop
886
886
887 # list of steps to perform discovery before pull
887 # list of steps to perform discovery before pull
888 pulldiscoveryorder = []
888 pulldiscoveryorder = []
889
889
890 # Mapping between step name and function
890 # Mapping between step name and function
891 #
891 #
892 # This exists to help extensions wrap steps if necessary
892 # This exists to help extensions wrap steps if necessary
893 pulldiscoverymapping = {}
893 pulldiscoverymapping = {}
894
894
895 def pulldiscovery(stepname):
895 def pulldiscovery(stepname):
896 """decorator for function performing discovery before pull
896 """decorator for function performing discovery before pull
897
897
898 The function is added to the step -> function mapping and appended to the
898 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
899 list of steps. Beware that decorated function will be added in order (this
900 may matter).
900 may matter).
901
901
902 You can only use this decorator for a new step, if you want to wrap a step
902 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."""
903 from an extension, change the pulldiscovery dictionary directly."""
904 def dec(func):
904 def dec(func):
905 assert stepname not in pulldiscoverymapping
905 assert stepname not in pulldiscoverymapping
906 pulldiscoverymapping[stepname] = func
906 pulldiscoverymapping[stepname] = func
907 pulldiscoveryorder.append(stepname)
907 pulldiscoveryorder.append(stepname)
908 return func
908 return func
909 return dec
909 return dec
910
910
911 def _pulldiscovery(pullop):
911 def _pulldiscovery(pullop):
912 """Run all discovery steps"""
912 """Run all discovery steps"""
913 for stepname in pulldiscoveryorder:
913 for stepname in pulldiscoveryorder:
914 step = pulldiscoverymapping[stepname]
914 step = pulldiscoverymapping[stepname]
915 step(pullop)
915 step(pullop)
916
916
917 @pulldiscovery('changegroup')
917 @pulldiscovery('changegroup')
918 def _pulldiscoverychangegroup(pullop):
918 def _pulldiscoverychangegroup(pullop):
919 """discovery phase for the pull
919 """discovery phase for the pull
920
920
921 Current handle changeset discovery only, will change handle all discovery
921 Current handle changeset discovery only, will change handle all discovery
922 at some point."""
922 at some point."""
923 tmp = discovery.findcommonincoming(pullop.repo.unfiltered(),
923 tmp = discovery.findcommonincoming(pullop.repo.unfiltered(),
924 pullop.remote,
924 pullop.remote,
925 heads=pullop.heads,
925 heads=pullop.heads,
926 force=pullop.force)
926 force=pullop.force)
927 pullop.common, pullop.fetch, pullop.rheads = tmp
927 pullop.common, pullop.fetch, pullop.rheads = tmp
928
928
929 def _pullbundle2(pullop):
929 def _pullbundle2(pullop):
930 """pull data using bundle2
930 """pull data using bundle2
931
931
932 For now, the only supported data are changegroup."""
932 For now, the only supported data are changegroup."""
933 remotecaps = bundle2.bundle2caps(pullop.remote)
933 remotecaps = bundle2.bundle2caps(pullop.remote)
934 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
934 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
935 # pulling changegroup
935 # pulling changegroup
936 pullop.stepsdone.add('changegroup')
936 pullop.stepsdone.add('changegroup')
937
937
938 kwargs['common'] = pullop.common
938 kwargs['common'] = pullop.common
939 kwargs['heads'] = pullop.heads or pullop.rheads
939 kwargs['heads'] = pullop.heads or pullop.rheads
940 kwargs['cg'] = pullop.fetch
940 kwargs['cg'] = pullop.fetch
941 if 'b2x:listkeys' in remotecaps:
941 if 'b2x:listkeys' in remotecaps:
942 kwargs['listkeys'] = ['phase', 'bookmarks']
942 kwargs['listkeys'] = ['phase', 'bookmarks']
943 if not pullop.fetch:
943 if not pullop.fetch:
944 pullop.repo.ui.status(_("no changes found\n"))
944 pullop.repo.ui.status(_("no changes found\n"))
945 pullop.cgresult = 0
945 pullop.cgresult = 0
946 else:
946 else:
947 if pullop.heads is None and list(pullop.common) == [nullid]:
947 if pullop.heads is None and list(pullop.common) == [nullid]:
948 pullop.repo.ui.status(_("requesting all changes\n"))
948 pullop.repo.ui.status(_("requesting all changes\n"))
949 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
949 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
950 remoteversions = bundle2.obsmarkersversion(remotecaps)
950 remoteversions = bundle2.obsmarkersversion(remotecaps)
951 if obsolete.commonversion(remoteversions) is not None:
951 if obsolete.commonversion(remoteversions) is not None:
952 kwargs['obsmarkers'] = True
952 kwargs['obsmarkers'] = True
953 pullop.stepsdone.add('obsmarkers')
953 pullop.stepsdone.add('obsmarkers')
954 _pullbundle2extraprepare(pullop, kwargs)
954 _pullbundle2extraprepare(pullop, kwargs)
955 if kwargs.keys() == ['format']:
955 if kwargs.keys() == ['format']:
956 return # nothing to pull
956 return # nothing to pull
957 bundle = pullop.remote.getbundle('pull', **kwargs)
957 bundle = pullop.remote.getbundle('pull', **kwargs)
958 try:
958 try:
959 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
959 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
960 except error.BundleValueError, exc:
960 except error.BundleValueError, exc:
961 raise util.Abort('missing support for %s' % exc)
961 raise util.Abort('missing support for %s' % exc)
962
962
963 if pullop.fetch:
963 if pullop.fetch:
964 changedheads = 0
964 changedheads = 0
965 pullop.cgresult = 1
965 pullop.cgresult = 1
966 for cg in op.records['changegroup']:
966 for cg in op.records['changegroup']:
967 ret = cg['return']
967 ret = cg['return']
968 # If any changegroup result is 0, return 0
968 # If any changegroup result is 0, return 0
969 if ret == 0:
969 if ret == 0:
970 pullop.cgresult = 0
970 pullop.cgresult = 0
971 break
971 break
972 if ret < -1:
972 if ret < -1:
973 changedheads += ret + 1
973 changedheads += ret + 1
974 elif ret > 1:
974 elif ret > 1:
975 changedheads += ret - 1
975 changedheads += ret - 1
976 if changedheads > 0:
976 if changedheads > 0:
977 pullop.cgresult = 1 + changedheads
977 pullop.cgresult = 1 + changedheads
978 elif changedheads < 0:
978 elif changedheads < 0:
979 pullop.cgresult = -1 + changedheads
979 pullop.cgresult = -1 + changedheads
980
980
981 # processing phases change
981 # processing phases change
982 for namespace, value in op.records['listkeys']:
982 for namespace, value in op.records['listkeys']:
983 if namespace == 'phases':
983 if namespace == 'phases':
984 _pullapplyphases(pullop, value)
984 _pullapplyphases(pullop, value)
985
985
986 # processing bookmark update
986 # processing bookmark update
987 for namespace, value in op.records['listkeys']:
987 for namespace, value in op.records['listkeys']:
988 if namespace == 'bookmarks':
988 if namespace == 'bookmarks':
989 pullop.remotebookmarks = value
989 pullop.remotebookmarks = value
990 _pullbookmarks(pullop)
990 _pullbookmarks(pullop)
991
991
992 def _pullbundle2extraprepare(pullop, kwargs):
992 def _pullbundle2extraprepare(pullop, kwargs):
993 """hook function so that extensions can extend the getbundle call"""
993 """hook function so that extensions can extend the getbundle call"""
994 pass
994 pass
995
995
996 def _pullchangeset(pullop):
996 def _pullchangeset(pullop):
997 """pull changeset from unbundle into the local repo"""
997 """pull changeset from unbundle into the local repo"""
998 # We delay the open of the transaction as late as possible so we
998 # 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
999 # don't open transaction for nothing or you break future useful
1000 # rollback call
1000 # rollback call
1001 if 'changegroup' in pullop.stepsdone:
1001 if 'changegroup' in pullop.stepsdone:
1002 return
1002 return
1003 pullop.stepsdone.add('changegroup')
1003 pullop.stepsdone.add('changegroup')
1004 if not pullop.fetch:
1004 if not pullop.fetch:
1005 pullop.repo.ui.status(_("no changes found\n"))
1005 pullop.repo.ui.status(_("no changes found\n"))
1006 pullop.cgresult = 0
1006 pullop.cgresult = 0
1007 return
1007 return
1008 pullop.gettransaction()
1008 pullop.gettransaction()
1009 if pullop.heads is None and list(pullop.common) == [nullid]:
1009 if pullop.heads is None and list(pullop.common) == [nullid]:
1010 pullop.repo.ui.status(_("requesting all changes\n"))
1010 pullop.repo.ui.status(_("requesting all changes\n"))
1011 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1011 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1012 # issue1320, avoid a race if remote changed after discovery
1012 # issue1320, avoid a race if remote changed after discovery
1013 pullop.heads = pullop.rheads
1013 pullop.heads = pullop.rheads
1014
1014
1015 if pullop.remote.capable('getbundle'):
1015 if pullop.remote.capable('getbundle'):
1016 # TODO: get bundlecaps from remote
1016 # TODO: get bundlecaps from remote
1017 cg = pullop.remote.getbundle('pull', common=pullop.common,
1017 cg = pullop.remote.getbundle('pull', common=pullop.common,
1018 heads=pullop.heads or pullop.rheads)
1018 heads=pullop.heads or pullop.rheads)
1019 elif pullop.heads is None:
1019 elif pullop.heads is None:
1020 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1020 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1021 elif not pullop.remote.capable('changegroupsubset'):
1021 elif not pullop.remote.capable('changegroupsubset'):
1022 raise util.Abort(_("partial pull cannot be done because "
1022 raise util.Abort(_("partial pull cannot be done because "
1023 "other repository doesn't support "
1023 "other repository doesn't support "
1024 "changegroupsubset."))
1024 "changegroupsubset."))
1025 else:
1025 else:
1026 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1026 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1027 pullop.cgresult = changegroup.addchangegroup(pullop.repo, cg, 'pull',
1027 pullop.cgresult = changegroup.addchangegroup(pullop.repo, cg, 'pull',
1028 pullop.remote.url())
1028 pullop.remote.url())
1029
1029
1030 def _pullphase(pullop):
1030 def _pullphase(pullop):
1031 # Get remote phases data from remote
1031 # Get remote phases data from remote
1032 if 'phases' in pullop.stepsdone:
1032 if 'phases' in pullop.stepsdone:
1033 return
1033 return
1034 remotephases = pullop.remote.listkeys('phases')
1034 remotephases = pullop.remote.listkeys('phases')
1035 _pullapplyphases(pullop, remotephases)
1035 _pullapplyphases(pullop, remotephases)
1036
1036
1037 def _pullapplyphases(pullop, remotephases):
1037 def _pullapplyphases(pullop, remotephases):
1038 """apply phase movement from observed remote state"""
1038 """apply phase movement from observed remote state"""
1039 if 'phases' in pullop.stepsdone:
1039 if 'phases' in pullop.stepsdone:
1040 return
1040 return
1041 pullop.stepsdone.add('phases')
1041 pullop.stepsdone.add('phases')
1042 publishing = bool(remotephases.get('publishing', False))
1042 publishing = bool(remotephases.get('publishing', False))
1043 if remotephases and not publishing:
1043 if remotephases and not publishing:
1044 # remote is new and unpublishing
1044 # remote is new and unpublishing
1045 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1045 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1046 pullop.pulledsubset,
1046 pullop.pulledsubset,
1047 remotephases)
1047 remotephases)
1048 dheads = pullop.pulledsubset
1048 dheads = pullop.pulledsubset
1049 else:
1049 else:
1050 # Remote is old or publishing all common changesets
1050 # Remote is old or publishing all common changesets
1051 # should be seen as public
1051 # should be seen as public
1052 pheads = pullop.pulledsubset
1052 pheads = pullop.pulledsubset
1053 dheads = []
1053 dheads = []
1054 unfi = pullop.repo.unfiltered()
1054 unfi = pullop.repo.unfiltered()
1055 phase = unfi._phasecache.phase
1055 phase = unfi._phasecache.phase
1056 rev = unfi.changelog.nodemap.get
1056 rev = unfi.changelog.nodemap.get
1057 public = phases.public
1057 public = phases.public
1058 draft = phases.draft
1058 draft = phases.draft
1059
1059
1060 # exclude changesets already public locally and update the others
1060 # exclude changesets already public locally and update the others
1061 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1061 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1062 if pheads:
1062 if pheads:
1063 tr = pullop.gettransaction()
1063 tr = pullop.gettransaction()
1064 phases.advanceboundary(pullop.repo, tr, public, pheads)
1064 phases.advanceboundary(pullop.repo, tr, public, pheads)
1065
1065
1066 # exclude changesets already draft locally and update the others
1066 # exclude changesets already draft locally and update the others
1067 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1067 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1068 if dheads:
1068 if dheads:
1069 tr = pullop.gettransaction()
1069 tr = pullop.gettransaction()
1070 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1070 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1071
1071
1072 def _pullbookmarks(pullop):
1072 def _pullbookmarks(pullop):
1073 """process the remote bookmark information to update the local one"""
1073 """process the remote bookmark information to update the local one"""
1074 if 'bookmarks' in pullop.stepsdone:
1074 if 'bookmarks' in pullop.stepsdone:
1075 return
1075 return
1076 pullop.stepsdone.add('bookmarks')
1076 pullop.stepsdone.add('bookmarks')
1077 repo = pullop.repo
1077 repo = pullop.repo
1078 remotebookmarks = pullop.remotebookmarks
1078 remotebookmarks = pullop.remotebookmarks
1079 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1079 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1080 pullop.remote.url(),
1080 pullop.remote.url(),
1081 pullop.gettransaction,
1081 pullop.gettransaction,
1082 explicit=pullop.explicitbookmarks)
1082 explicit=pullop.explicitbookmarks)
1083
1083
1084 def _pullobsolete(pullop):
1084 def _pullobsolete(pullop):
1085 """utility function to pull obsolete markers from a remote
1085 """utility function to pull obsolete markers from a remote
1086
1086
1087 The `gettransaction` is function that return the pull transaction, creating
1087 The `gettransaction` is function that return the pull transaction, creating
1088 one if necessary. We return the transaction to inform the calling code that
1088 one if necessary. We return the transaction to inform the calling code that
1089 a new transaction have been created (when applicable).
1089 a new transaction have been created (when applicable).
1090
1090
1091 Exists mostly to allow overriding for experimentation purpose"""
1091 Exists mostly to allow overriding for experimentation purpose"""
1092 if 'obsmarkers' in pullop.stepsdone:
1092 if 'obsmarkers' in pullop.stepsdone:
1093 return
1093 return
1094 pullop.stepsdone.add('obsmarkers')
1094 pullop.stepsdone.add('obsmarkers')
1095 tr = None
1095 tr = None
1096 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1096 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1097 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1097 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1098 remoteobs = pullop.remote.listkeys('obsolete')
1098 remoteobs = pullop.remote.listkeys('obsolete')
1099 if 'dump0' in remoteobs:
1099 if 'dump0' in remoteobs:
1100 tr = pullop.gettransaction()
1100 tr = pullop.gettransaction()
1101 for key in sorted(remoteobs, reverse=True):
1101 for key in sorted(remoteobs, reverse=True):
1102 if key.startswith('dump'):
1102 if key.startswith('dump'):
1103 data = base85.b85decode(remoteobs[key])
1103 data = base85.b85decode(remoteobs[key])
1104 pullop.repo.obsstore.mergemarkers(tr, data)
1104 pullop.repo.obsstore.mergemarkers(tr, data)
1105 pullop.repo.invalidatevolatilesets()
1105 pullop.repo.invalidatevolatilesets()
1106 return tr
1106 return tr
1107
1107
1108 def caps20to10(repo):
1108 def caps20to10(repo):
1109 """return a set with appropriate options to use bundle20 during getbundle"""
1109 """return a set with appropriate options to use bundle20 during getbundle"""
1110 caps = set(['HG2Y'])
1110 caps = set(['HG2Y'])
1111 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1111 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1112 caps.add('bundle2=' + urllib.quote(capsblob))
1112 caps.add('bundle2=' + urllib.quote(capsblob))
1113 return caps
1113 return caps
1114
1114
1115 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1115 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1116 getbundle2partsorder = []
1116 getbundle2partsorder = []
1117
1117
1118 # Mapping between step name and function
1118 # Mapping between step name and function
1119 #
1119 #
1120 # This exists to help extensions wrap steps if necessary
1120 # This exists to help extensions wrap steps if necessary
1121 getbundle2partsmapping = {}
1121 getbundle2partsmapping = {}
1122
1122
1123 def getbundle2partsgenerator(stepname):
1123 def getbundle2partsgenerator(stepname):
1124 """decorator for function generating bundle2 part for getbundle
1124 """decorator for function generating bundle2 part for getbundle
1125
1125
1126 The function is added to the step -> function mapping and appended to the
1126 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
1127 list of steps. Beware that decorated functions will be added in order
1128 (this may matter).
1128 (this may matter).
1129
1129
1130 You can only use this decorator for new steps, if you want to wrap a step
1130 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."""
1131 from an extension, attack the getbundle2partsmapping dictionary directly."""
1132 def dec(func):
1132 def dec(func):
1133 assert stepname not in getbundle2partsmapping
1133 assert stepname not in getbundle2partsmapping
1134 getbundle2partsmapping[stepname] = func
1134 getbundle2partsmapping[stepname] = func
1135 getbundle2partsorder.append(stepname)
1135 getbundle2partsorder.append(stepname)
1136 return func
1136 return func
1137 return dec
1137 return dec
1138
1138
1139 def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
1139 def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
1140 **kwargs):
1140 **kwargs):
1141 """return a full bundle (with potentially multiple kind of parts)
1141 """return a full bundle (with potentially multiple kind of parts)
1142
1142
1143 Could be a bundle HG10 or a bundle HG2Y depending on bundlecaps
1143 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
1144 passed. For now, the bundle can contain only changegroup, but this will
1145 changes when more part type will be available for bundle2.
1145 changes when more part type will be available for bundle2.
1146
1146
1147 This is different from changegroup.getchangegroup that only returns an HG10
1147 This is different from changegroup.getchangegroup that only returns an HG10
1148 changegroup bundle. They may eventually get reunited in the future when we
1148 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.
1149 have a clearer idea of the API we what to query different data.
1150
1150
1151 The implementation is at a very early stage and will get massive rework
1151 The implementation is at a very early stage and will get massive rework
1152 when the API of bundle is refined.
1152 when the API of bundle is refined.
1153 """
1153 """
1154 # bundle10 case
1154 # bundle10 case
1155 if bundlecaps is None or 'HG2Y' not in bundlecaps:
1155 if bundlecaps is None or 'HG2Y' not in bundlecaps:
1156 if bundlecaps and not kwargs.get('cg', True):
1156 if bundlecaps and not kwargs.get('cg', True):
1157 raise ValueError(_('request for bundle10 must include changegroup'))
1157 raise ValueError(_('request for bundle10 must include changegroup'))
1158
1158
1159 if kwargs:
1159 if kwargs:
1160 raise ValueError(_('unsupported getbundle arguments: %s')
1160 raise ValueError(_('unsupported getbundle arguments: %s')
1161 % ', '.join(sorted(kwargs.keys())))
1161 % ', '.join(sorted(kwargs.keys())))
1162 return changegroup.getchangegroup(repo, source, heads=heads,
1162 return changegroup.getchangegroup(repo, source, heads=heads,
1163 common=common, bundlecaps=bundlecaps)
1163 common=common, bundlecaps=bundlecaps)
1164
1164
1165 # bundle20 case
1165 # bundle20 case
1166 b2caps = {}
1166 b2caps = {}
1167 for bcaps in bundlecaps:
1167 for bcaps in bundlecaps:
1168 if bcaps.startswith('bundle2='):
1168 if bcaps.startswith('bundle2='):
1169 blob = urllib.unquote(bcaps[len('bundle2='):])
1169 blob = urllib.unquote(bcaps[len('bundle2='):])
1170 b2caps.update(bundle2.decodecaps(blob))
1170 b2caps.update(bundle2.decodecaps(blob))
1171 bundler = bundle2.bundle20(repo.ui, b2caps)
1171 bundler = bundle2.bundle20(repo.ui, b2caps)
1172
1172
1173 for name in getbundle2partsorder:
1173 for name in getbundle2partsorder:
1174 func = getbundle2partsmapping[name]
1174 func = getbundle2partsmapping[name]
1175 kwargs['heads'] = heads
1175 kwargs['heads'] = heads
1176 kwargs['common'] = common
1176 kwargs['common'] = common
1177 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1177 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1178 **kwargs)
1178 **kwargs)
1179
1179
1180 return util.chunkbuffer(bundler.getchunks())
1180 return util.chunkbuffer(bundler.getchunks())
1181
1181
1182 @getbundle2partsgenerator('changegroup')
1182 @getbundle2partsgenerator('changegroup')
1183 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1183 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1184 b2caps=None, heads=None, common=None, **kwargs):
1184 b2caps=None, heads=None, common=None, **kwargs):
1185 """add a changegroup part to the requested bundle"""
1185 """add a changegroup part to the requested bundle"""
1186 cg = None
1186 cg = None
1187 if kwargs.get('cg', True):
1187 if kwargs.get('cg', True):
1188 # build changegroup bundle here.
1188 # build changegroup bundle here.
1189 version = None
1190 cgversions = b2caps.get('b2x:changegroup')
1191 if cgversions is None:
1189 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1192 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1190 common=common, bundlecaps=bundlecaps)
1193 common=common,
1194 bundlecaps=bundlecaps)
1195 else:
1196 cgversions = [v for v in cgversions if v in changegroup.packermap]
1197 if not cgversions:
1198 raise ValueError(_('no common changegroup version'))
1199 version = max(cgversions)
1200 cg = changegroup.getchangegroupraw(repo, source, heads=heads,
1201 common=common,
1202 bundlecaps=bundlecaps,
1203 version=version)
1191
1204
1192 if cg:
1205 if cg:
1193 bundler.newpart('b2x:changegroup', data=cg)
1206 part = bundler.newpart('b2x:changegroup', data=cg)
1207 if version is not None:
1208 part.addparam('version', version)
1194
1209
1195 @getbundle2partsgenerator('listkeys')
1210 @getbundle2partsgenerator('listkeys')
1196 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1211 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1197 b2caps=None, **kwargs):
1212 b2caps=None, **kwargs):
1198 """add parts containing listkeys namespaces to the requested bundle"""
1213 """add parts containing listkeys namespaces to the requested bundle"""
1199 listkeys = kwargs.get('listkeys', ())
1214 listkeys = kwargs.get('listkeys', ())
1200 for namespace in listkeys:
1215 for namespace in listkeys:
1201 part = bundler.newpart('b2x:listkeys')
1216 part = bundler.newpart('b2x:listkeys')
1202 part.addparam('namespace', namespace)
1217 part.addparam('namespace', namespace)
1203 keys = repo.listkeys(namespace).items()
1218 keys = repo.listkeys(namespace).items()
1204 part.data = pushkey.encodekeys(keys)
1219 part.data = pushkey.encodekeys(keys)
1205
1220
1206 @getbundle2partsgenerator('obsmarkers')
1221 @getbundle2partsgenerator('obsmarkers')
1207 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1222 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1208 b2caps=None, heads=None, **kwargs):
1223 b2caps=None, heads=None, **kwargs):
1209 """add an obsolescence markers part to the requested bundle"""
1224 """add an obsolescence markers part to the requested bundle"""
1210 if kwargs.get('obsmarkers', False):
1225 if kwargs.get('obsmarkers', False):
1211 if heads is None:
1226 if heads is None:
1212 heads = repo.heads()
1227 heads = repo.heads()
1213 subset = [c.node() for c in repo.set('::%ln', heads)]
1228 subset = [c.node() for c in repo.set('::%ln', heads)]
1214 markers = repo.obsstore.relevantmarkers(subset)
1229 markers = repo.obsstore.relevantmarkers(subset)
1215 buildobsmarkerspart(bundler, markers)
1230 buildobsmarkerspart(bundler, markers)
1216
1231
1217 def check_heads(repo, their_heads, context):
1232 def check_heads(repo, their_heads, context):
1218 """check if the heads of a repo have been modified
1233 """check if the heads of a repo have been modified
1219
1234
1220 Used by peer for unbundling.
1235 Used by peer for unbundling.
1221 """
1236 """
1222 heads = repo.heads()
1237 heads = repo.heads()
1223 heads_hash = util.sha1(''.join(sorted(heads))).digest()
1238 heads_hash = util.sha1(''.join(sorted(heads))).digest()
1224 if not (their_heads == ['force'] or their_heads == heads or
1239 if not (their_heads == ['force'] or their_heads == heads or
1225 their_heads == ['hashed', heads_hash]):
1240 their_heads == ['hashed', heads_hash]):
1226 # someone else committed/pushed/unbundled while we
1241 # someone else committed/pushed/unbundled while we
1227 # were transferring data
1242 # were transferring data
1228 raise error.PushRaced('repository changed while %s - '
1243 raise error.PushRaced('repository changed while %s - '
1229 'please try again' % context)
1244 'please try again' % context)
1230
1245
1231 def unbundle(repo, cg, heads, source, url):
1246 def unbundle(repo, cg, heads, source, url):
1232 """Apply a bundle to a repo.
1247 """Apply a bundle to a repo.
1233
1248
1234 this function makes sure the repo is locked during the application and have
1249 this function makes sure the repo is locked during the application and have
1235 mechanism to check that no push race occurred between the creation of the
1250 mechanism to check that no push race occurred between the creation of the
1236 bundle and its application.
1251 bundle and its application.
1237
1252
1238 If the push was raced as PushRaced exception is raised."""
1253 If the push was raced as PushRaced exception is raised."""
1239 r = 0
1254 r = 0
1240 # need a transaction when processing a bundle2 stream
1255 # need a transaction when processing a bundle2 stream
1241 tr = None
1256 tr = None
1242 lock = repo.lock()
1257 lock = repo.lock()
1243 try:
1258 try:
1244 check_heads(repo, heads, 'uploading changes')
1259 check_heads(repo, heads, 'uploading changes')
1245 # push can proceed
1260 # push can proceed
1246 if util.safehasattr(cg, 'params'):
1261 if util.safehasattr(cg, 'params'):
1247 try:
1262 try:
1248 tr = repo.transaction('unbundle')
1263 tr = repo.transaction('unbundle')
1249 tr.hookargs['source'] = source
1264 tr.hookargs['source'] = source
1250 tr.hookargs['url'] = url
1265 tr.hookargs['url'] = url
1251 tr.hookargs['bundle2-exp'] = '1'
1266 tr.hookargs['bundle2-exp'] = '1'
1252 r = bundle2.processbundle(repo, cg, lambda: tr).reply
1267 r = bundle2.processbundle(repo, cg, lambda: tr).reply
1253 cl = repo.unfiltered().changelog
1268 cl = repo.unfiltered().changelog
1254 p = cl.writepending() and repo.root or ""
1269 p = cl.writepending() and repo.root or ""
1255 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
1270 repo.hook('b2x-pretransactionclose', throw=True, pending=p,
1256 **tr.hookargs)
1271 **tr.hookargs)
1257 tr.close()
1272 tr.close()
1258 hookargs = dict(tr.hookargs)
1273 hookargs = dict(tr.hookargs)
1259 def runhooks():
1274 def runhooks():
1260 repo.hook('b2x-transactionclose', **hookargs)
1275 repo.hook('b2x-transactionclose', **hookargs)
1261 repo._afterlock(runhooks)
1276 repo._afterlock(runhooks)
1262 except Exception, exc:
1277 except Exception, exc:
1263 exc.duringunbundle2 = True
1278 exc.duringunbundle2 = True
1264 raise
1279 raise
1265 else:
1280 else:
1266 r = changegroup.addchangegroup(repo, cg, source, url)
1281 r = changegroup.addchangegroup(repo, cg, source, url)
1267 finally:
1282 finally:
1268 if tr is not None:
1283 if tr is not None:
1269 tr.release()
1284 tr.release()
1270 lock.release()
1285 lock.release()
1271 return r
1286 return r
General Comments 0
You need to be logged in to leave comments. Login now