##// END OF EJS Templates
infinitepush: don't wrap bundle2.processparts while calling `hg unbundle`...
Pulkit Goyal -
r37222:e5cd0ef5 default
parent child Browse files
Show More
@@ -1,1119 +1,1126 b''
1 # Infinite push
1 # Infinite push
2 #
2 #
3 # Copyright 2016 Facebook, Inc.
3 # Copyright 2016 Facebook, Inc.
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 """ store some pushes in a remote blob store on the server (EXPERIMENTAL)
7 """ store some pushes in a remote blob store on the server (EXPERIMENTAL)
8
8
9 [infinitepush]
9 [infinitepush]
10 # Server-side and client-side option. Pattern of the infinitepush bookmark
10 # Server-side and client-side option. Pattern of the infinitepush bookmark
11 branchpattern = PATTERN
11 branchpattern = PATTERN
12
12
13 # Server or client
13 # Server or client
14 server = False
14 server = False
15
15
16 # Server-side option. Possible values: 'disk' or 'sql'. Fails if not set
16 # Server-side option. Possible values: 'disk' or 'sql'. Fails if not set
17 indextype = disk
17 indextype = disk
18
18
19 # Server-side option. Used only if indextype=sql.
19 # Server-side option. Used only if indextype=sql.
20 # Format: 'IP:PORT:DB_NAME:USER:PASSWORD'
20 # Format: 'IP:PORT:DB_NAME:USER:PASSWORD'
21 sqlhost = IP:PORT:DB_NAME:USER:PASSWORD
21 sqlhost = IP:PORT:DB_NAME:USER:PASSWORD
22
22
23 # Server-side option. Used only if indextype=disk.
23 # Server-side option. Used only if indextype=disk.
24 # Filesystem path to the index store
24 # Filesystem path to the index store
25 indexpath = PATH
25 indexpath = PATH
26
26
27 # Server-side option. Possible values: 'disk' or 'external'
27 # Server-side option. Possible values: 'disk' or 'external'
28 # Fails if not set
28 # Fails if not set
29 storetype = disk
29 storetype = disk
30
30
31 # Server-side option.
31 # Server-side option.
32 # Path to the binary that will save bundle to the bundlestore
32 # Path to the binary that will save bundle to the bundlestore
33 # Formatted cmd line will be passed to it (see `put_args`)
33 # Formatted cmd line will be passed to it (see `put_args`)
34 put_binary = put
34 put_binary = put
35
35
36 # Serser-side option. Used only if storetype=external.
36 # Serser-side option. Used only if storetype=external.
37 # Format cmd-line string for put binary. Placeholder: {filename}
37 # Format cmd-line string for put binary. Placeholder: {filename}
38 put_args = {filename}
38 put_args = {filename}
39
39
40 # Server-side option.
40 # Server-side option.
41 # Path to the binary that get bundle from the bundlestore.
41 # Path to the binary that get bundle from the bundlestore.
42 # Formatted cmd line will be passed to it (see `get_args`)
42 # Formatted cmd line will be passed to it (see `get_args`)
43 get_binary = get
43 get_binary = get
44
44
45 # Serser-side option. Used only if storetype=external.
45 # Serser-side option. Used only if storetype=external.
46 # Format cmd-line string for get binary. Placeholders: {filename} {handle}
46 # Format cmd-line string for get binary. Placeholders: {filename} {handle}
47 get_args = {filename} {handle}
47 get_args = {filename} {handle}
48
48
49 # Server-side option
49 # Server-side option
50 logfile = FIlE
50 logfile = FIlE
51
51
52 # Server-side option
52 # Server-side option
53 loglevel = DEBUG
53 loglevel = DEBUG
54
54
55 # Server-side option. Used only if indextype=sql.
55 # Server-side option. Used only if indextype=sql.
56 # Sets mysql wait_timeout option.
56 # Sets mysql wait_timeout option.
57 waittimeout = 300
57 waittimeout = 300
58
58
59 # Server-side option. Used only if indextype=sql.
59 # Server-side option. Used only if indextype=sql.
60 # Sets mysql innodb_lock_wait_timeout option.
60 # Sets mysql innodb_lock_wait_timeout option.
61 locktimeout = 120
61 locktimeout = 120
62
62
63 # Server-side option. Used only if indextype=sql.
63 # Server-side option. Used only if indextype=sql.
64 # Name of the repository
64 # Name of the repository
65 reponame = ''
65 reponame = ''
66
66
67 # Client-side option. Used by --list-remote option. List of remote scratch
67 # Client-side option. Used by --list-remote option. List of remote scratch
68 # patterns to list if no patterns are specified.
68 # patterns to list if no patterns are specified.
69 defaultremotepatterns = ['*']
69 defaultremotepatterns = ['*']
70
70
71 # Instructs infinitepush to forward all received bundle2 parts to the
71 # Instructs infinitepush to forward all received bundle2 parts to the
72 # bundle for storage. Defaults to False.
72 # bundle for storage. Defaults to False.
73 storeallparts = True
73 storeallparts = True
74
74
75 [remotenames]
75 [remotenames]
76 # Client-side option
76 # Client-side option
77 # This option should be set only if remotenames extension is enabled.
77 # This option should be set only if remotenames extension is enabled.
78 # Whether remote bookmarks are tracked by remotenames extension.
78 # Whether remote bookmarks are tracked by remotenames extension.
79 bookmarks = True
79 bookmarks = True
80 """
80 """
81
81
82 from __future__ import absolute_import
82 from __future__ import absolute_import
83
83
84 import collections
84 import collections
85 import contextlib
85 import contextlib
86 import errno
86 import errno
87 import functools
87 import functools
88 import logging
88 import logging
89 import os
89 import os
90 import random
90 import random
91 import re
91 import re
92 import socket
92 import socket
93 import subprocess
93 import subprocess
94 import tempfile
94 import tempfile
95 import time
95 import time
96
96
97 from mercurial.node import (
97 from mercurial.node import (
98 bin,
98 bin,
99 hex,
99 hex,
100 )
100 )
101
101
102 from mercurial.i18n import _
102 from mercurial.i18n import _
103
103
104 from mercurial.utils import (
104 from mercurial.utils import (
105 procutil,
105 procutil,
106 stringutil,
106 stringutil,
107 )
107 )
108
108
109 from mercurial import (
109 from mercurial import (
110 bundle2,
110 bundle2,
111 changegroup,
111 changegroup,
112 commands,
112 commands,
113 discovery,
113 discovery,
114 encoding,
114 encoding,
115 error,
115 error,
116 exchange,
116 exchange,
117 extensions,
117 extensions,
118 hg,
118 hg,
119 localrepo,
119 localrepo,
120 peer,
120 peer,
121 phases,
121 phases,
122 pushkey,
122 pushkey,
123 registrar,
123 registrar,
124 util,
124 util,
125 wireproto,
125 wireproto,
126 )
126 )
127
127
128 from . import (
128 from . import (
129 bundleparts,
129 bundleparts,
130 common,
130 common,
131 )
131 )
132
132
133 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
133 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
134 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
134 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
135 # be specifying the version(s) of Mercurial they are tested with, or
135 # be specifying the version(s) of Mercurial they are tested with, or
136 # leave the attribute unspecified.
136 # leave the attribute unspecified.
137 testedwith = 'ships-with-hg-core'
137 testedwith = 'ships-with-hg-core'
138
138
139 configtable = {}
139 configtable = {}
140 configitem = registrar.configitem(configtable)
140 configitem = registrar.configitem(configtable)
141
141
142 configitem('infinitepush', 'server',
142 configitem('infinitepush', 'server',
143 default=False,
143 default=False,
144 )
144 )
145 configitem('infinitepush', 'storetype',
145 configitem('infinitepush', 'storetype',
146 default='',
146 default='',
147 )
147 )
148 configitem('infinitepush', 'indextype',
148 configitem('infinitepush', 'indextype',
149 default='',
149 default='',
150 )
150 )
151 configitem('infinitepush', 'indexpath',
151 configitem('infinitepush', 'indexpath',
152 default='',
152 default='',
153 )
153 )
154 configitem('infinitepush', 'storeallparts',
154 configitem('infinitepush', 'storeallparts',
155 default=False,
155 default=False,
156 )
156 )
157 configitem('infinitepush', 'reponame',
157 configitem('infinitepush', 'reponame',
158 default='',
158 default='',
159 )
159 )
160 configitem('scratchbranch', 'storepath',
160 configitem('scratchbranch', 'storepath',
161 default='',
161 default='',
162 )
162 )
163 configitem('infinitepush', 'branchpattern',
163 configitem('infinitepush', 'branchpattern',
164 default='',
164 default='',
165 )
165 )
166 configitem('experimental', 'server-bundlestore-bookmark',
166 configitem('experimental', 'server-bundlestore-bookmark',
167 default='',
167 default='',
168 )
168 )
169 configitem('experimental', 'infinitepush-scratchpush',
169 configitem('experimental', 'infinitepush-scratchpush',
170 default=False,
170 default=False,
171 )
171 )
172
172
173 experimental = 'experimental'
173 experimental = 'experimental'
174 configbookmark = 'server-bundlestore-bookmark'
174 configbookmark = 'server-bundlestore-bookmark'
175 configscratchpush = 'infinitepush-scratchpush'
175 configscratchpush = 'infinitepush-scratchpush'
176
176
177 scratchbranchparttype = bundleparts.scratchbranchparttype
177 scratchbranchparttype = bundleparts.scratchbranchparttype
178 revsetpredicate = registrar.revsetpredicate()
178 revsetpredicate = registrar.revsetpredicate()
179 templatekeyword = registrar.templatekeyword()
179 templatekeyword = registrar.templatekeyword()
180 _scratchbranchmatcher = lambda x: False
180 _scratchbranchmatcher = lambda x: False
181 _maybehash = re.compile(r'^[a-f0-9]+$').search
181 _maybehash = re.compile(r'^[a-f0-9]+$').search
182
182
183 def _buildexternalbundlestore(ui):
183 def _buildexternalbundlestore(ui):
184 put_args = ui.configlist('infinitepush', 'put_args', [])
184 put_args = ui.configlist('infinitepush', 'put_args', [])
185 put_binary = ui.config('infinitepush', 'put_binary')
185 put_binary = ui.config('infinitepush', 'put_binary')
186 if not put_binary:
186 if not put_binary:
187 raise error.Abort('put binary is not specified')
187 raise error.Abort('put binary is not specified')
188 get_args = ui.configlist('infinitepush', 'get_args', [])
188 get_args = ui.configlist('infinitepush', 'get_args', [])
189 get_binary = ui.config('infinitepush', 'get_binary')
189 get_binary = ui.config('infinitepush', 'get_binary')
190 if not get_binary:
190 if not get_binary:
191 raise error.Abort('get binary is not specified')
191 raise error.Abort('get binary is not specified')
192 from . import store
192 from . import store
193 return store.externalbundlestore(put_binary, put_args, get_binary, get_args)
193 return store.externalbundlestore(put_binary, put_args, get_binary, get_args)
194
194
195 def _buildsqlindex(ui):
195 def _buildsqlindex(ui):
196 sqlhost = ui.config('infinitepush', 'sqlhost')
196 sqlhost = ui.config('infinitepush', 'sqlhost')
197 if not sqlhost:
197 if not sqlhost:
198 raise error.Abort(_('please set infinitepush.sqlhost'))
198 raise error.Abort(_('please set infinitepush.sqlhost'))
199 host, port, db, user, password = sqlhost.split(':')
199 host, port, db, user, password = sqlhost.split(':')
200 reponame = ui.config('infinitepush', 'reponame')
200 reponame = ui.config('infinitepush', 'reponame')
201 if not reponame:
201 if not reponame:
202 raise error.Abort(_('please set infinitepush.reponame'))
202 raise error.Abort(_('please set infinitepush.reponame'))
203
203
204 logfile = ui.config('infinitepush', 'logfile', '')
204 logfile = ui.config('infinitepush', 'logfile', '')
205 waittimeout = ui.configint('infinitepush', 'waittimeout', 300)
205 waittimeout = ui.configint('infinitepush', 'waittimeout', 300)
206 locktimeout = ui.configint('infinitepush', 'locktimeout', 120)
206 locktimeout = ui.configint('infinitepush', 'locktimeout', 120)
207 from . import sqlindexapi
207 from . import sqlindexapi
208 return sqlindexapi.sqlindexapi(
208 return sqlindexapi.sqlindexapi(
209 reponame, host, port, db, user, password,
209 reponame, host, port, db, user, password,
210 logfile, _getloglevel(ui), waittimeout=waittimeout,
210 logfile, _getloglevel(ui), waittimeout=waittimeout,
211 locktimeout=locktimeout)
211 locktimeout=locktimeout)
212
212
213 def _getloglevel(ui):
213 def _getloglevel(ui):
214 loglevel = ui.config('infinitepush', 'loglevel', 'DEBUG')
214 loglevel = ui.config('infinitepush', 'loglevel', 'DEBUG')
215 numeric_loglevel = getattr(logging, loglevel.upper(), None)
215 numeric_loglevel = getattr(logging, loglevel.upper(), None)
216 if not isinstance(numeric_loglevel, int):
216 if not isinstance(numeric_loglevel, int):
217 raise error.Abort(_('invalid log level %s') % loglevel)
217 raise error.Abort(_('invalid log level %s') % loglevel)
218 return numeric_loglevel
218 return numeric_loglevel
219
219
220 def _tryhoist(ui, remotebookmark):
220 def _tryhoist(ui, remotebookmark):
221 '''returns a bookmarks with hoisted part removed
221 '''returns a bookmarks with hoisted part removed
222
222
223 Remotenames extension has a 'hoist' config that allows to use remote
223 Remotenames extension has a 'hoist' config that allows to use remote
224 bookmarks without specifying remote path. For example, 'hg update master'
224 bookmarks without specifying remote path. For example, 'hg update master'
225 works as well as 'hg update remote/master'. We want to allow the same in
225 works as well as 'hg update remote/master'. We want to allow the same in
226 infinitepush.
226 infinitepush.
227 '''
227 '''
228
228
229 if common.isremotebooksenabled(ui):
229 if common.isremotebooksenabled(ui):
230 hoist = ui.config('remotenames', 'hoist') + '/'
230 hoist = ui.config('remotenames', 'hoist') + '/'
231 if remotebookmark.startswith(hoist):
231 if remotebookmark.startswith(hoist):
232 return remotebookmark[len(hoist):]
232 return remotebookmark[len(hoist):]
233 return remotebookmark
233 return remotebookmark
234
234
235 class bundlestore(object):
235 class bundlestore(object):
236 def __init__(self, repo):
236 def __init__(self, repo):
237 self._repo = repo
237 self._repo = repo
238 storetype = self._repo.ui.config('infinitepush', 'storetype', '')
238 storetype = self._repo.ui.config('infinitepush', 'storetype', '')
239 if storetype == 'disk':
239 if storetype == 'disk':
240 from . import store
240 from . import store
241 self.store = store.filebundlestore(self._repo.ui, self._repo)
241 self.store = store.filebundlestore(self._repo.ui, self._repo)
242 elif storetype == 'external':
242 elif storetype == 'external':
243 self.store = _buildexternalbundlestore(self._repo.ui)
243 self.store = _buildexternalbundlestore(self._repo.ui)
244 else:
244 else:
245 raise error.Abort(
245 raise error.Abort(
246 _('unknown infinitepush store type specified %s') % storetype)
246 _('unknown infinitepush store type specified %s') % storetype)
247
247
248 indextype = self._repo.ui.config('infinitepush', 'indextype', '')
248 indextype = self._repo.ui.config('infinitepush', 'indextype', '')
249 if indextype == 'disk':
249 if indextype == 'disk':
250 from . import fileindexapi
250 from . import fileindexapi
251 self.index = fileindexapi.fileindexapi(self._repo)
251 self.index = fileindexapi.fileindexapi(self._repo)
252 elif indextype == 'sql':
252 elif indextype == 'sql':
253 self.index = _buildsqlindex(self._repo.ui)
253 self.index = _buildsqlindex(self._repo.ui)
254 else:
254 else:
255 raise error.Abort(
255 raise error.Abort(
256 _('unknown infinitepush index type specified %s') % indextype)
256 _('unknown infinitepush index type specified %s') % indextype)
257
257
258 def _isserver(ui):
258 def _isserver(ui):
259 return ui.configbool('infinitepush', 'server')
259 return ui.configbool('infinitepush', 'server')
260
260
261 def reposetup(ui, repo):
261 def reposetup(ui, repo):
262 if _isserver(ui) and repo.local():
262 if _isserver(ui) and repo.local():
263 repo.bundlestore = bundlestore(repo)
263 repo.bundlestore = bundlestore(repo)
264
264
265 def extsetup(ui):
265 def extsetup(ui):
266 commonsetup(ui)
266 commonsetup(ui)
267 if _isserver(ui):
267 if _isserver(ui):
268 serverextsetup(ui)
268 serverextsetup(ui)
269 else:
269 else:
270 clientextsetup(ui)
270 clientextsetup(ui)
271
271
272 def commonsetup(ui):
272 def commonsetup(ui):
273 wireproto.commands['listkeyspatterns'] = (
273 wireproto.commands['listkeyspatterns'] = (
274 wireprotolistkeyspatterns, 'namespace patterns')
274 wireprotolistkeyspatterns, 'namespace patterns')
275 scratchbranchpat = ui.config('infinitepush', 'branchpattern')
275 scratchbranchpat = ui.config('infinitepush', 'branchpattern')
276 if scratchbranchpat:
276 if scratchbranchpat:
277 global _scratchbranchmatcher
277 global _scratchbranchmatcher
278 kind, pat, _scratchbranchmatcher = \
278 kind, pat, _scratchbranchmatcher = \
279 stringutil.stringmatcher(scratchbranchpat)
279 stringutil.stringmatcher(scratchbranchpat)
280
280
281 def serverextsetup(ui):
281 def serverextsetup(ui):
282 origpushkeyhandler = bundle2.parthandlermapping['pushkey']
282 origpushkeyhandler = bundle2.parthandlermapping['pushkey']
283
283
284 def newpushkeyhandler(*args, **kwargs):
284 def newpushkeyhandler(*args, **kwargs):
285 bundle2pushkey(origpushkeyhandler, *args, **kwargs)
285 bundle2pushkey(origpushkeyhandler, *args, **kwargs)
286 newpushkeyhandler.params = origpushkeyhandler.params
286 newpushkeyhandler.params = origpushkeyhandler.params
287 bundle2.parthandlermapping['pushkey'] = newpushkeyhandler
287 bundle2.parthandlermapping['pushkey'] = newpushkeyhandler
288
288
289 orighandlephasehandler = bundle2.parthandlermapping['phase-heads']
289 orighandlephasehandler = bundle2.parthandlermapping['phase-heads']
290 newphaseheadshandler = lambda *args, **kwargs: \
290 newphaseheadshandler = lambda *args, **kwargs: \
291 bundle2handlephases(orighandlephasehandler, *args, **kwargs)
291 bundle2handlephases(orighandlephasehandler, *args, **kwargs)
292 newphaseheadshandler.params = orighandlephasehandler.params
292 newphaseheadshandler.params = orighandlephasehandler.params
293 bundle2.parthandlermapping['phase-heads'] = newphaseheadshandler
293 bundle2.parthandlermapping['phase-heads'] = newphaseheadshandler
294
294
295 extensions.wrapfunction(localrepo.localrepository, 'listkeys',
295 extensions.wrapfunction(localrepo.localrepository, 'listkeys',
296 localrepolistkeys)
296 localrepolistkeys)
297 wireproto.commands['lookup'] = (
297 wireproto.commands['lookup'] = (
298 _lookupwrap(wireproto.commands['lookup'][0]), 'key')
298 _lookupwrap(wireproto.commands['lookup'][0]), 'key')
299 extensions.wrapfunction(exchange, 'getbundlechunks', getbundlechunks)
299 extensions.wrapfunction(exchange, 'getbundlechunks', getbundlechunks)
300
300
301 extensions.wrapfunction(bundle2, 'processparts', processparts)
301 extensions.wrapfunction(bundle2, 'processparts', processparts)
302
302
303 def clientextsetup(ui):
303 def clientextsetup(ui):
304 entry = extensions.wrapcommand(commands.table, 'push', _push)
304 entry = extensions.wrapcommand(commands.table, 'push', _push)
305
305
306 entry[1].append(
306 entry[1].append(
307 ('', 'bundle-store', None,
307 ('', 'bundle-store', None,
308 _('force push to go to bundle store (EXPERIMENTAL)')))
308 _('force push to go to bundle store (EXPERIMENTAL)')))
309
309
310 extensions.wrapcommand(commands.table, 'pull', _pull)
310 extensions.wrapcommand(commands.table, 'pull', _pull)
311
311
312 extensions.wrapfunction(discovery, 'checkheads', _checkheads)
312 extensions.wrapfunction(discovery, 'checkheads', _checkheads)
313
313
314 wireproto.wirepeer.listkeyspatterns = listkeyspatterns
314 wireproto.wirepeer.listkeyspatterns = listkeyspatterns
315
315
316 partorder = exchange.b2partsgenorder
316 partorder = exchange.b2partsgenorder
317 index = partorder.index('changeset')
317 index = partorder.index('changeset')
318 partorder.insert(
318 partorder.insert(
319 index, partorder.pop(partorder.index(scratchbranchparttype)))
319 index, partorder.pop(partorder.index(scratchbranchparttype)))
320
320
321 def _checkheads(orig, pushop):
321 def _checkheads(orig, pushop):
322 if pushop.ui.configbool(experimental, configscratchpush, False):
322 if pushop.ui.configbool(experimental, configscratchpush, False):
323 return
323 return
324 return orig(pushop)
324 return orig(pushop)
325
325
326 def wireprotolistkeyspatterns(repo, proto, namespace, patterns):
326 def wireprotolistkeyspatterns(repo, proto, namespace, patterns):
327 patterns = wireproto.decodelist(patterns)
327 patterns = wireproto.decodelist(patterns)
328 d = repo.listkeys(encoding.tolocal(namespace), patterns).iteritems()
328 d = repo.listkeys(encoding.tolocal(namespace), patterns).iteritems()
329 return pushkey.encodekeys(d)
329 return pushkey.encodekeys(d)
330
330
331 def localrepolistkeys(orig, self, namespace, patterns=None):
331 def localrepolistkeys(orig, self, namespace, patterns=None):
332 if namespace == 'bookmarks' and patterns:
332 if namespace == 'bookmarks' and patterns:
333 index = self.bundlestore.index
333 index = self.bundlestore.index
334 results = {}
334 results = {}
335 bookmarks = orig(self, namespace)
335 bookmarks = orig(self, namespace)
336 for pattern in patterns:
336 for pattern in patterns:
337 results.update(index.getbookmarks(pattern))
337 results.update(index.getbookmarks(pattern))
338 if pattern.endswith('*'):
338 if pattern.endswith('*'):
339 pattern = 're:^' + pattern[:-1] + '.*'
339 pattern = 're:^' + pattern[:-1] + '.*'
340 kind, pat, matcher = stringutil.stringmatcher(pattern)
340 kind, pat, matcher = stringutil.stringmatcher(pattern)
341 for bookmark, node in bookmarks.iteritems():
341 for bookmark, node in bookmarks.iteritems():
342 if matcher(bookmark):
342 if matcher(bookmark):
343 results[bookmark] = node
343 results[bookmark] = node
344 return results
344 return results
345 else:
345 else:
346 return orig(self, namespace)
346 return orig(self, namespace)
347
347
348 @peer.batchable
348 @peer.batchable
349 def listkeyspatterns(self, namespace, patterns):
349 def listkeyspatterns(self, namespace, patterns):
350 if not self.capable('pushkey'):
350 if not self.capable('pushkey'):
351 yield {}, None
351 yield {}, None
352 f = peer.future()
352 f = peer.future()
353 self.ui.debug('preparing listkeys for "%s" with pattern "%s"\n' %
353 self.ui.debug('preparing listkeys for "%s" with pattern "%s"\n' %
354 (namespace, patterns))
354 (namespace, patterns))
355 yield {
355 yield {
356 'namespace': encoding.fromlocal(namespace),
356 'namespace': encoding.fromlocal(namespace),
357 'patterns': wireproto.encodelist(patterns)
357 'patterns': wireproto.encodelist(patterns)
358 }, f
358 }, f
359 d = f.value
359 d = f.value
360 self.ui.debug('received listkey for "%s": %i bytes\n'
360 self.ui.debug('received listkey for "%s": %i bytes\n'
361 % (namespace, len(d)))
361 % (namespace, len(d)))
362 yield pushkey.decodekeys(d)
362 yield pushkey.decodekeys(d)
363
363
364 def _readbundlerevs(bundlerepo):
364 def _readbundlerevs(bundlerepo):
365 return list(bundlerepo.revs('bundle()'))
365 return list(bundlerepo.revs('bundle()'))
366
366
367 def _includefilelogstobundle(bundlecaps, bundlerepo, bundlerevs, ui):
367 def _includefilelogstobundle(bundlecaps, bundlerepo, bundlerevs, ui):
368 '''Tells remotefilelog to include all changed files to the changegroup
368 '''Tells remotefilelog to include all changed files to the changegroup
369
369
370 By default remotefilelog doesn't include file content to the changegroup.
370 By default remotefilelog doesn't include file content to the changegroup.
371 But we need to include it if we are fetching from bundlestore.
371 But we need to include it if we are fetching from bundlestore.
372 '''
372 '''
373 changedfiles = set()
373 changedfiles = set()
374 cl = bundlerepo.changelog
374 cl = bundlerepo.changelog
375 for r in bundlerevs:
375 for r in bundlerevs:
376 # [3] means changed files
376 # [3] means changed files
377 changedfiles.update(cl.read(r)[3])
377 changedfiles.update(cl.read(r)[3])
378 if not changedfiles:
378 if not changedfiles:
379 return bundlecaps
379 return bundlecaps
380
380
381 changedfiles = '\0'.join(changedfiles)
381 changedfiles = '\0'.join(changedfiles)
382 newcaps = []
382 newcaps = []
383 appended = False
383 appended = False
384 for cap in (bundlecaps or []):
384 for cap in (bundlecaps or []):
385 if cap.startswith('excludepattern='):
385 if cap.startswith('excludepattern='):
386 newcaps.append('\0'.join((cap, changedfiles)))
386 newcaps.append('\0'.join((cap, changedfiles)))
387 appended = True
387 appended = True
388 else:
388 else:
389 newcaps.append(cap)
389 newcaps.append(cap)
390 if not appended:
390 if not appended:
391 # Not found excludepattern cap. Just append it
391 # Not found excludepattern cap. Just append it
392 newcaps.append('excludepattern=' + changedfiles)
392 newcaps.append('excludepattern=' + changedfiles)
393
393
394 return newcaps
394 return newcaps
395
395
396 def _rebundle(bundlerepo, bundleroots, unknownhead):
396 def _rebundle(bundlerepo, bundleroots, unknownhead):
397 '''
397 '''
398 Bundle may include more revision then user requested. For example,
398 Bundle may include more revision then user requested. For example,
399 if user asks for revision but bundle also consists its descendants.
399 if user asks for revision but bundle also consists its descendants.
400 This function will filter out all revision that user is not requested.
400 This function will filter out all revision that user is not requested.
401 '''
401 '''
402 parts = []
402 parts = []
403
403
404 version = '02'
404 version = '02'
405 outgoing = discovery.outgoing(bundlerepo, commonheads=bundleroots,
405 outgoing = discovery.outgoing(bundlerepo, commonheads=bundleroots,
406 missingheads=[unknownhead])
406 missingheads=[unknownhead])
407 cgstream = changegroup.makestream(bundlerepo, outgoing, version, 'pull')
407 cgstream = changegroup.makestream(bundlerepo, outgoing, version, 'pull')
408 cgstream = util.chunkbuffer(cgstream).read()
408 cgstream = util.chunkbuffer(cgstream).read()
409 cgpart = bundle2.bundlepart('changegroup', data=cgstream)
409 cgpart = bundle2.bundlepart('changegroup', data=cgstream)
410 cgpart.addparam('version', version)
410 cgpart.addparam('version', version)
411 parts.append(cgpart)
411 parts.append(cgpart)
412
412
413 return parts
413 return parts
414
414
415 def _getbundleroots(oldrepo, bundlerepo, bundlerevs):
415 def _getbundleroots(oldrepo, bundlerepo, bundlerevs):
416 cl = bundlerepo.changelog
416 cl = bundlerepo.changelog
417 bundleroots = []
417 bundleroots = []
418 for rev in bundlerevs:
418 for rev in bundlerevs:
419 node = cl.node(rev)
419 node = cl.node(rev)
420 parents = cl.parents(node)
420 parents = cl.parents(node)
421 for parent in parents:
421 for parent in parents:
422 # include all revs that exist in the main repo
422 # include all revs that exist in the main repo
423 # to make sure that bundle may apply client-side
423 # to make sure that bundle may apply client-side
424 if parent in oldrepo:
424 if parent in oldrepo:
425 bundleroots.append(parent)
425 bundleroots.append(parent)
426 return bundleroots
426 return bundleroots
427
427
428 def _needsrebundling(head, bundlerepo):
428 def _needsrebundling(head, bundlerepo):
429 bundleheads = list(bundlerepo.revs('heads(bundle())'))
429 bundleheads = list(bundlerepo.revs('heads(bundle())'))
430 return not (len(bundleheads) == 1 and
430 return not (len(bundleheads) == 1 and
431 bundlerepo[bundleheads[0]].node() == head)
431 bundlerepo[bundleheads[0]].node() == head)
432
432
433 def _generateoutputparts(head, bundlerepo, bundleroots, bundlefile):
433 def _generateoutputparts(head, bundlerepo, bundleroots, bundlefile):
434 '''generates bundle that will be send to the user
434 '''generates bundle that will be send to the user
435
435
436 returns tuple with raw bundle string and bundle type
436 returns tuple with raw bundle string and bundle type
437 '''
437 '''
438 parts = []
438 parts = []
439 if not _needsrebundling(head, bundlerepo):
439 if not _needsrebundling(head, bundlerepo):
440 with util.posixfile(bundlefile, "rb") as f:
440 with util.posixfile(bundlefile, "rb") as f:
441 unbundler = exchange.readbundle(bundlerepo.ui, f, bundlefile)
441 unbundler = exchange.readbundle(bundlerepo.ui, f, bundlefile)
442 if isinstance(unbundler, changegroup.cg1unpacker):
442 if isinstance(unbundler, changegroup.cg1unpacker):
443 part = bundle2.bundlepart('changegroup',
443 part = bundle2.bundlepart('changegroup',
444 data=unbundler._stream.read())
444 data=unbundler._stream.read())
445 part.addparam('version', '01')
445 part.addparam('version', '01')
446 parts.append(part)
446 parts.append(part)
447 elif isinstance(unbundler, bundle2.unbundle20):
447 elif isinstance(unbundler, bundle2.unbundle20):
448 haschangegroup = False
448 haschangegroup = False
449 for part in unbundler.iterparts():
449 for part in unbundler.iterparts():
450 if part.type == 'changegroup':
450 if part.type == 'changegroup':
451 haschangegroup = True
451 haschangegroup = True
452 newpart = bundle2.bundlepart(part.type, data=part.read())
452 newpart = bundle2.bundlepart(part.type, data=part.read())
453 for key, value in part.params.iteritems():
453 for key, value in part.params.iteritems():
454 newpart.addparam(key, value)
454 newpart.addparam(key, value)
455 parts.append(newpart)
455 parts.append(newpart)
456
456
457 if not haschangegroup:
457 if not haschangegroup:
458 raise error.Abort(
458 raise error.Abort(
459 'unexpected bundle without changegroup part, ' +
459 'unexpected bundle without changegroup part, ' +
460 'head: %s' % hex(head),
460 'head: %s' % hex(head),
461 hint='report to administrator')
461 hint='report to administrator')
462 else:
462 else:
463 raise error.Abort('unknown bundle type')
463 raise error.Abort('unknown bundle type')
464 else:
464 else:
465 parts = _rebundle(bundlerepo, bundleroots, head)
465 parts = _rebundle(bundlerepo, bundleroots, head)
466
466
467 return parts
467 return parts
468
468
469 def getbundlechunks(orig, repo, source, heads=None, bundlecaps=None, **kwargs):
469 def getbundlechunks(orig, repo, source, heads=None, bundlecaps=None, **kwargs):
470 heads = heads or []
470 heads = heads or []
471 # newheads are parents of roots of scratch bundles that were requested
471 # newheads are parents of roots of scratch bundles that were requested
472 newphases = {}
472 newphases = {}
473 scratchbundles = []
473 scratchbundles = []
474 newheads = []
474 newheads = []
475 scratchheads = []
475 scratchheads = []
476 nodestobundle = {}
476 nodestobundle = {}
477 allbundlestocleanup = []
477 allbundlestocleanup = []
478 try:
478 try:
479 for head in heads:
479 for head in heads:
480 if head not in repo.changelog.nodemap:
480 if head not in repo.changelog.nodemap:
481 if head not in nodestobundle:
481 if head not in nodestobundle:
482 newbundlefile = common.downloadbundle(repo, head)
482 newbundlefile = common.downloadbundle(repo, head)
483 bundlepath = "bundle:%s+%s" % (repo.root, newbundlefile)
483 bundlepath = "bundle:%s+%s" % (repo.root, newbundlefile)
484 bundlerepo = hg.repository(repo.ui, bundlepath)
484 bundlerepo = hg.repository(repo.ui, bundlepath)
485
485
486 allbundlestocleanup.append((bundlerepo, newbundlefile))
486 allbundlestocleanup.append((bundlerepo, newbundlefile))
487 bundlerevs = set(_readbundlerevs(bundlerepo))
487 bundlerevs = set(_readbundlerevs(bundlerepo))
488 bundlecaps = _includefilelogstobundle(
488 bundlecaps = _includefilelogstobundle(
489 bundlecaps, bundlerepo, bundlerevs, repo.ui)
489 bundlecaps, bundlerepo, bundlerevs, repo.ui)
490 cl = bundlerepo.changelog
490 cl = bundlerepo.changelog
491 bundleroots = _getbundleroots(repo, bundlerepo, bundlerevs)
491 bundleroots = _getbundleroots(repo, bundlerepo, bundlerevs)
492 for rev in bundlerevs:
492 for rev in bundlerevs:
493 node = cl.node(rev)
493 node = cl.node(rev)
494 newphases[hex(node)] = str(phases.draft)
494 newphases[hex(node)] = str(phases.draft)
495 nodestobundle[node] = (bundlerepo, bundleroots,
495 nodestobundle[node] = (bundlerepo, bundleroots,
496 newbundlefile)
496 newbundlefile)
497
497
498 scratchbundles.append(
498 scratchbundles.append(
499 _generateoutputparts(head, *nodestobundle[head]))
499 _generateoutputparts(head, *nodestobundle[head]))
500 newheads.extend(bundleroots)
500 newheads.extend(bundleroots)
501 scratchheads.append(head)
501 scratchheads.append(head)
502 finally:
502 finally:
503 for bundlerepo, bundlefile in allbundlestocleanup:
503 for bundlerepo, bundlefile in allbundlestocleanup:
504 bundlerepo.close()
504 bundlerepo.close()
505 try:
505 try:
506 os.unlink(bundlefile)
506 os.unlink(bundlefile)
507 except (IOError, OSError):
507 except (IOError, OSError):
508 # if we can't cleanup the file then just ignore the error,
508 # if we can't cleanup the file then just ignore the error,
509 # no need to fail
509 # no need to fail
510 pass
510 pass
511
511
512 pullfrombundlestore = bool(scratchbundles)
512 pullfrombundlestore = bool(scratchbundles)
513 wrappedchangegrouppart = False
513 wrappedchangegrouppart = False
514 wrappedlistkeys = False
514 wrappedlistkeys = False
515 oldchangegrouppart = exchange.getbundle2partsmapping['changegroup']
515 oldchangegrouppart = exchange.getbundle2partsmapping['changegroup']
516 try:
516 try:
517 def _changegrouppart(bundler, *args, **kwargs):
517 def _changegrouppart(bundler, *args, **kwargs):
518 # Order is important here. First add non-scratch part
518 # Order is important here. First add non-scratch part
519 # and only then add parts with scratch bundles because
519 # and only then add parts with scratch bundles because
520 # non-scratch part contains parents of roots of scratch bundles.
520 # non-scratch part contains parents of roots of scratch bundles.
521 result = oldchangegrouppart(bundler, *args, **kwargs)
521 result = oldchangegrouppart(bundler, *args, **kwargs)
522 for bundle in scratchbundles:
522 for bundle in scratchbundles:
523 for part in bundle:
523 for part in bundle:
524 bundler.addpart(part)
524 bundler.addpart(part)
525 return result
525 return result
526
526
527 exchange.getbundle2partsmapping['changegroup'] = _changegrouppart
527 exchange.getbundle2partsmapping['changegroup'] = _changegrouppart
528 wrappedchangegrouppart = True
528 wrappedchangegrouppart = True
529
529
530 def _listkeys(orig, self, namespace):
530 def _listkeys(orig, self, namespace):
531 origvalues = orig(self, namespace)
531 origvalues = orig(self, namespace)
532 if namespace == 'phases' and pullfrombundlestore:
532 if namespace == 'phases' and pullfrombundlestore:
533 if origvalues.get('publishing') == 'True':
533 if origvalues.get('publishing') == 'True':
534 # Make repo non-publishing to preserve draft phase
534 # Make repo non-publishing to preserve draft phase
535 del origvalues['publishing']
535 del origvalues['publishing']
536 origvalues.update(newphases)
536 origvalues.update(newphases)
537 return origvalues
537 return origvalues
538
538
539 extensions.wrapfunction(localrepo.localrepository, 'listkeys',
539 extensions.wrapfunction(localrepo.localrepository, 'listkeys',
540 _listkeys)
540 _listkeys)
541 wrappedlistkeys = True
541 wrappedlistkeys = True
542 heads = list((set(newheads) | set(heads)) - set(scratchheads))
542 heads = list((set(newheads) | set(heads)) - set(scratchheads))
543 result = orig(repo, source, heads=heads,
543 result = orig(repo, source, heads=heads,
544 bundlecaps=bundlecaps, **kwargs)
544 bundlecaps=bundlecaps, **kwargs)
545 finally:
545 finally:
546 if wrappedchangegrouppart:
546 if wrappedchangegrouppart:
547 exchange.getbundle2partsmapping['changegroup'] = oldchangegrouppart
547 exchange.getbundle2partsmapping['changegroup'] = oldchangegrouppart
548 if wrappedlistkeys:
548 if wrappedlistkeys:
549 extensions.unwrapfunction(localrepo.localrepository, 'listkeys',
549 extensions.unwrapfunction(localrepo.localrepository, 'listkeys',
550 _listkeys)
550 _listkeys)
551 return result
551 return result
552
552
553 def _lookupwrap(orig):
553 def _lookupwrap(orig):
554 def _lookup(repo, proto, key):
554 def _lookup(repo, proto, key):
555 localkey = encoding.tolocal(key)
555 localkey = encoding.tolocal(key)
556
556
557 if isinstance(localkey, str) and _scratchbranchmatcher(localkey):
557 if isinstance(localkey, str) and _scratchbranchmatcher(localkey):
558 scratchnode = repo.bundlestore.index.getnode(localkey)
558 scratchnode = repo.bundlestore.index.getnode(localkey)
559 if scratchnode:
559 if scratchnode:
560 return "%s %s\n" % (1, scratchnode)
560 return "%s %s\n" % (1, scratchnode)
561 else:
561 else:
562 return "%s %s\n" % (0, 'scratch branch %s not found' % localkey)
562 return "%s %s\n" % (0, 'scratch branch %s not found' % localkey)
563 else:
563 else:
564 try:
564 try:
565 r = hex(repo.lookup(localkey))
565 r = hex(repo.lookup(localkey))
566 return "%s %s\n" % (1, r)
566 return "%s %s\n" % (1, r)
567 except Exception as inst:
567 except Exception as inst:
568 if repo.bundlestore.index.getbundle(localkey):
568 if repo.bundlestore.index.getbundle(localkey):
569 return "%s %s\n" % (1, localkey)
569 return "%s %s\n" % (1, localkey)
570 else:
570 else:
571 r = str(inst)
571 r = str(inst)
572 return "%s %s\n" % (0, r)
572 return "%s %s\n" % (0, r)
573 return _lookup
573 return _lookup
574
574
575 def _pull(orig, ui, repo, source="default", **opts):
575 def _pull(orig, ui, repo, source="default", **opts):
576 # Copy paste from `pull` command
576 # Copy paste from `pull` command
577 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
577 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
578
578
579 scratchbookmarks = {}
579 scratchbookmarks = {}
580 unfi = repo.unfiltered()
580 unfi = repo.unfiltered()
581 unknownnodes = []
581 unknownnodes = []
582 for rev in opts.get('rev', []):
582 for rev in opts.get('rev', []):
583 if rev not in unfi:
583 if rev not in unfi:
584 unknownnodes.append(rev)
584 unknownnodes.append(rev)
585 if opts.get('bookmark'):
585 if opts.get('bookmark'):
586 bookmarks = []
586 bookmarks = []
587 revs = opts.get('rev') or []
587 revs = opts.get('rev') or []
588 for bookmark in opts.get('bookmark'):
588 for bookmark in opts.get('bookmark'):
589 if _scratchbranchmatcher(bookmark):
589 if _scratchbranchmatcher(bookmark):
590 # rev is not known yet
590 # rev is not known yet
591 # it will be fetched with listkeyspatterns next
591 # it will be fetched with listkeyspatterns next
592 scratchbookmarks[bookmark] = 'REVTOFETCH'
592 scratchbookmarks[bookmark] = 'REVTOFETCH'
593 else:
593 else:
594 bookmarks.append(bookmark)
594 bookmarks.append(bookmark)
595
595
596 if scratchbookmarks:
596 if scratchbookmarks:
597 other = hg.peer(repo, opts, source)
597 other = hg.peer(repo, opts, source)
598 fetchedbookmarks = other.listkeyspatterns(
598 fetchedbookmarks = other.listkeyspatterns(
599 'bookmarks', patterns=scratchbookmarks)
599 'bookmarks', patterns=scratchbookmarks)
600 for bookmark in scratchbookmarks:
600 for bookmark in scratchbookmarks:
601 if bookmark not in fetchedbookmarks:
601 if bookmark not in fetchedbookmarks:
602 raise error.Abort('remote bookmark %s not found!' %
602 raise error.Abort('remote bookmark %s not found!' %
603 bookmark)
603 bookmark)
604 scratchbookmarks[bookmark] = fetchedbookmarks[bookmark]
604 scratchbookmarks[bookmark] = fetchedbookmarks[bookmark]
605 revs.append(fetchedbookmarks[bookmark])
605 revs.append(fetchedbookmarks[bookmark])
606 opts['bookmark'] = bookmarks
606 opts['bookmark'] = bookmarks
607 opts['rev'] = revs
607 opts['rev'] = revs
608
608
609 if scratchbookmarks or unknownnodes:
609 if scratchbookmarks or unknownnodes:
610 # Set anyincoming to True
610 # Set anyincoming to True
611 extensions.wrapfunction(discovery, 'findcommonincoming',
611 extensions.wrapfunction(discovery, 'findcommonincoming',
612 _findcommonincoming)
612 _findcommonincoming)
613 try:
613 try:
614 # Remote scratch bookmarks will be deleted because remotenames doesn't
614 # Remote scratch bookmarks will be deleted because remotenames doesn't
615 # know about them. Let's save it before pull and restore after
615 # know about them. Let's save it before pull and restore after
616 remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, source)
616 remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, source)
617 result = orig(ui, repo, source, **opts)
617 result = orig(ui, repo, source, **opts)
618 # TODO(stash): race condition is possible
618 # TODO(stash): race condition is possible
619 # if scratch bookmarks was updated right after orig.
619 # if scratch bookmarks was updated right after orig.
620 # But that's unlikely and shouldn't be harmful.
620 # But that's unlikely and shouldn't be harmful.
621 if common.isremotebooksenabled(ui):
621 if common.isremotebooksenabled(ui):
622 remotescratchbookmarks.update(scratchbookmarks)
622 remotescratchbookmarks.update(scratchbookmarks)
623 _saveremotebookmarks(repo, remotescratchbookmarks, source)
623 _saveremotebookmarks(repo, remotescratchbookmarks, source)
624 else:
624 else:
625 _savelocalbookmarks(repo, scratchbookmarks)
625 _savelocalbookmarks(repo, scratchbookmarks)
626 return result
626 return result
627 finally:
627 finally:
628 if scratchbookmarks:
628 if scratchbookmarks:
629 extensions.unwrapfunction(discovery, 'findcommonincoming')
629 extensions.unwrapfunction(discovery, 'findcommonincoming')
630
630
631 def _readscratchremotebookmarks(ui, repo, other):
631 def _readscratchremotebookmarks(ui, repo, other):
632 if common.isremotebooksenabled(ui):
632 if common.isremotebooksenabled(ui):
633 remotenamesext = extensions.find('remotenames')
633 remotenamesext = extensions.find('remotenames')
634 remotepath = remotenamesext.activepath(repo.ui, other)
634 remotepath = remotenamesext.activepath(repo.ui, other)
635 result = {}
635 result = {}
636 # Let's refresh remotenames to make sure we have it up to date
636 # Let's refresh remotenames to make sure we have it up to date
637 # Seems that `repo.names['remotebookmarks']` may return stale bookmarks
637 # Seems that `repo.names['remotebookmarks']` may return stale bookmarks
638 # and it results in deleting scratch bookmarks. Our best guess how to
638 # and it results in deleting scratch bookmarks. Our best guess how to
639 # fix it is to use `clearnames()`
639 # fix it is to use `clearnames()`
640 repo._remotenames.clearnames()
640 repo._remotenames.clearnames()
641 for remotebookmark in repo.names['remotebookmarks'].listnames(repo):
641 for remotebookmark in repo.names['remotebookmarks'].listnames(repo):
642 path, bookname = remotenamesext.splitremotename(remotebookmark)
642 path, bookname = remotenamesext.splitremotename(remotebookmark)
643 if path == remotepath and _scratchbranchmatcher(bookname):
643 if path == remotepath and _scratchbranchmatcher(bookname):
644 nodes = repo.names['remotebookmarks'].nodes(repo,
644 nodes = repo.names['remotebookmarks'].nodes(repo,
645 remotebookmark)
645 remotebookmark)
646 if nodes:
646 if nodes:
647 result[bookname] = hex(nodes[0])
647 result[bookname] = hex(nodes[0])
648 return result
648 return result
649 else:
649 else:
650 return {}
650 return {}
651
651
652 def _saveremotebookmarks(repo, newbookmarks, remote):
652 def _saveremotebookmarks(repo, newbookmarks, remote):
653 remotenamesext = extensions.find('remotenames')
653 remotenamesext = extensions.find('remotenames')
654 remotepath = remotenamesext.activepath(repo.ui, remote)
654 remotepath = remotenamesext.activepath(repo.ui, remote)
655 branches = collections.defaultdict(list)
655 branches = collections.defaultdict(list)
656 bookmarks = {}
656 bookmarks = {}
657 remotenames = remotenamesext.readremotenames(repo)
657 remotenames = remotenamesext.readremotenames(repo)
658 for hexnode, nametype, remote, rname in remotenames:
658 for hexnode, nametype, remote, rname in remotenames:
659 if remote != remotepath:
659 if remote != remotepath:
660 continue
660 continue
661 if nametype == 'bookmarks':
661 if nametype == 'bookmarks':
662 if rname in newbookmarks:
662 if rname in newbookmarks:
663 # It's possible if we have a normal bookmark that matches
663 # It's possible if we have a normal bookmark that matches
664 # scratch branch pattern. In this case just use the current
664 # scratch branch pattern. In this case just use the current
665 # bookmark node
665 # bookmark node
666 del newbookmarks[rname]
666 del newbookmarks[rname]
667 bookmarks[rname] = hexnode
667 bookmarks[rname] = hexnode
668 elif nametype == 'branches':
668 elif nametype == 'branches':
669 # saveremotenames expects 20 byte binary nodes for branches
669 # saveremotenames expects 20 byte binary nodes for branches
670 branches[rname].append(bin(hexnode))
670 branches[rname].append(bin(hexnode))
671
671
672 for bookmark, hexnode in newbookmarks.iteritems():
672 for bookmark, hexnode in newbookmarks.iteritems():
673 bookmarks[bookmark] = hexnode
673 bookmarks[bookmark] = hexnode
674 remotenamesext.saveremotenames(repo, remotepath, branches, bookmarks)
674 remotenamesext.saveremotenames(repo, remotepath, branches, bookmarks)
675
675
676 def _savelocalbookmarks(repo, bookmarks):
676 def _savelocalbookmarks(repo, bookmarks):
677 if not bookmarks:
677 if not bookmarks:
678 return
678 return
679 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
679 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
680 changes = []
680 changes = []
681 for scratchbook, node in bookmarks.iteritems():
681 for scratchbook, node in bookmarks.iteritems():
682 changectx = repo[node]
682 changectx = repo[node]
683 changes.append((scratchbook, changectx.node()))
683 changes.append((scratchbook, changectx.node()))
684 repo._bookmarks.applychanges(repo, tr, changes)
684 repo._bookmarks.applychanges(repo, tr, changes)
685
685
686 def _findcommonincoming(orig, *args, **kwargs):
686 def _findcommonincoming(orig, *args, **kwargs):
687 common, inc, remoteheads = orig(*args, **kwargs)
687 common, inc, remoteheads = orig(*args, **kwargs)
688 return common, True, remoteheads
688 return common, True, remoteheads
689
689
690 def _push(orig, ui, repo, dest=None, *args, **opts):
690 def _push(orig, ui, repo, dest=None, *args, **opts):
691
691
692 bookmark = opts.get('bookmark')
692 bookmark = opts.get('bookmark')
693 # we only support pushing one infinitepush bookmark at once
693 # we only support pushing one infinitepush bookmark at once
694 if len(bookmark) == 1:
694 if len(bookmark) == 1:
695 bookmark = bookmark[0]
695 bookmark = bookmark[0]
696 else:
696 else:
697 bookmark = ''
697 bookmark = ''
698
698
699 oldphasemove = None
699 oldphasemove = None
700 overrides = {(experimental, configbookmark): bookmark}
700 overrides = {(experimental, configbookmark): bookmark}
701
701
702 with ui.configoverride(overrides, 'infinitepush'):
702 with ui.configoverride(overrides, 'infinitepush'):
703 scratchpush = opts.get('bundle_store')
703 scratchpush = opts.get('bundle_store')
704 if _scratchbranchmatcher(bookmark):
704 if _scratchbranchmatcher(bookmark):
705 scratchpush = True
705 scratchpush = True
706 # bundle2 can be sent back after push (for example, bundle2
706 # bundle2 can be sent back after push (for example, bundle2
707 # containing `pushkey` part to update bookmarks)
707 # containing `pushkey` part to update bookmarks)
708 ui.setconfig(experimental, 'bundle2.pushback', True)
708 ui.setconfig(experimental, 'bundle2.pushback', True)
709
709
710 if scratchpush:
710 if scratchpush:
711 # this is an infinitepush, we don't want the bookmark to be applied
711 # this is an infinitepush, we don't want the bookmark to be applied
712 # rather that should be stored in the bundlestore
712 # rather that should be stored in the bundlestore
713 opts['bookmark'] = []
713 opts['bookmark'] = []
714 ui.setconfig(experimental, configscratchpush, True)
714 ui.setconfig(experimental, configscratchpush, True)
715 oldphasemove = extensions.wrapfunction(exchange,
715 oldphasemove = extensions.wrapfunction(exchange,
716 '_localphasemove',
716 '_localphasemove',
717 _phasemove)
717 _phasemove)
718 # Copy-paste from `push` command
718 # Copy-paste from `push` command
719 path = ui.paths.getpath(dest, default=('default-push', 'default'))
719 path = ui.paths.getpath(dest, default=('default-push', 'default'))
720 if not path:
720 if not path:
721 raise error.Abort(_('default repository not configured!'),
721 raise error.Abort(_('default repository not configured!'),
722 hint=_("see 'hg help config.paths'"))
722 hint=_("see 'hg help config.paths'"))
723 destpath = path.pushloc or path.loc
723 destpath = path.pushloc or path.loc
724 # Remote scratch bookmarks will be deleted because remotenames doesn't
724 # Remote scratch bookmarks will be deleted because remotenames doesn't
725 # know about them. Let's save it before push and restore after
725 # know about them. Let's save it before push and restore after
726 remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, destpath)
726 remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, destpath)
727 result = orig(ui, repo, dest, *args, **opts)
727 result = orig(ui, repo, dest, *args, **opts)
728 if common.isremotebooksenabled(ui):
728 if common.isremotebooksenabled(ui):
729 if bookmark and scratchpush:
729 if bookmark and scratchpush:
730 other = hg.peer(repo, opts, destpath)
730 other = hg.peer(repo, opts, destpath)
731 fetchedbookmarks = other.listkeyspatterns('bookmarks',
731 fetchedbookmarks = other.listkeyspatterns('bookmarks',
732 patterns=[bookmark])
732 patterns=[bookmark])
733 remotescratchbookmarks.update(fetchedbookmarks)
733 remotescratchbookmarks.update(fetchedbookmarks)
734 _saveremotebookmarks(repo, remotescratchbookmarks, destpath)
734 _saveremotebookmarks(repo, remotescratchbookmarks, destpath)
735 if oldphasemove:
735 if oldphasemove:
736 exchange._localphasemove = oldphasemove
736 exchange._localphasemove = oldphasemove
737 return result
737 return result
738
738
739 def _deleteinfinitepushbookmarks(ui, repo, path, names):
739 def _deleteinfinitepushbookmarks(ui, repo, path, names):
740 """Prune remote names by removing the bookmarks we don't want anymore,
740 """Prune remote names by removing the bookmarks we don't want anymore,
741 then writing the result back to disk
741 then writing the result back to disk
742 """
742 """
743 remotenamesext = extensions.find('remotenames')
743 remotenamesext = extensions.find('remotenames')
744
744
745 # remotename format is:
745 # remotename format is:
746 # (node, nametype ("branches" or "bookmarks"), remote, name)
746 # (node, nametype ("branches" or "bookmarks"), remote, name)
747 nametype_idx = 1
747 nametype_idx = 1
748 remote_idx = 2
748 remote_idx = 2
749 name_idx = 3
749 name_idx = 3
750 remotenames = [remotename for remotename in \
750 remotenames = [remotename for remotename in \
751 remotenamesext.readremotenames(repo) \
751 remotenamesext.readremotenames(repo) \
752 if remotename[remote_idx] == path]
752 if remotename[remote_idx] == path]
753 remote_bm_names = [remotename[name_idx] for remotename in \
753 remote_bm_names = [remotename[name_idx] for remotename in \
754 remotenames if remotename[nametype_idx] == "bookmarks"]
754 remotenames if remotename[nametype_idx] == "bookmarks"]
755
755
756 for name in names:
756 for name in names:
757 if name not in remote_bm_names:
757 if name not in remote_bm_names:
758 raise error.Abort(_("infinitepush bookmark '{}' does not exist "
758 raise error.Abort(_("infinitepush bookmark '{}' does not exist "
759 "in path '{}'").format(name, path))
759 "in path '{}'").format(name, path))
760
760
761 bookmarks = {}
761 bookmarks = {}
762 branches = collections.defaultdict(list)
762 branches = collections.defaultdict(list)
763 for node, nametype, remote, name in remotenames:
763 for node, nametype, remote, name in remotenames:
764 if nametype == "bookmarks" and name not in names:
764 if nametype == "bookmarks" and name not in names:
765 bookmarks[name] = node
765 bookmarks[name] = node
766 elif nametype == "branches":
766 elif nametype == "branches":
767 # saveremotenames wants binary nodes for branches
767 # saveremotenames wants binary nodes for branches
768 branches[name].append(bin(node))
768 branches[name].append(bin(node))
769
769
770 remotenamesext.saveremotenames(repo, path, branches, bookmarks)
770 remotenamesext.saveremotenames(repo, path, branches, bookmarks)
771
771
772 def _phasemove(orig, pushop, nodes, phase=phases.public):
772 def _phasemove(orig, pushop, nodes, phase=phases.public):
773 """prevent commits from being marked public
773 """prevent commits from being marked public
774
774
775 Since these are going to a scratch branch, they aren't really being
775 Since these are going to a scratch branch, they aren't really being
776 published."""
776 published."""
777
777
778 if phase != phases.public:
778 if phase != phases.public:
779 orig(pushop, nodes, phase)
779 orig(pushop, nodes, phase)
780
780
781 @exchange.b2partsgenerator(scratchbranchparttype)
781 @exchange.b2partsgenerator(scratchbranchparttype)
782 def partgen(pushop, bundler):
782 def partgen(pushop, bundler):
783 bookmark = pushop.ui.config(experimental, configbookmark)
783 bookmark = pushop.ui.config(experimental, configbookmark)
784 scratchpush = pushop.ui.configbool(experimental, configscratchpush)
784 scratchpush = pushop.ui.configbool(experimental, configscratchpush)
785 if 'changesets' in pushop.stepsdone or not scratchpush:
785 if 'changesets' in pushop.stepsdone or not scratchpush:
786 return
786 return
787
787
788 if scratchbranchparttype not in bundle2.bundle2caps(pushop.remote):
788 if scratchbranchparttype not in bundle2.bundle2caps(pushop.remote):
789 return
789 return
790
790
791 pushop.stepsdone.add('changesets')
791 pushop.stepsdone.add('changesets')
792 if not pushop.outgoing.missing:
792 if not pushop.outgoing.missing:
793 pushop.ui.status(_('no changes found\n'))
793 pushop.ui.status(_('no changes found\n'))
794 pushop.cgresult = 0
794 pushop.cgresult = 0
795 return
795 return
796
796
797 # This parameter tells the server that the following bundle is an
797 # This parameter tells the server that the following bundle is an
798 # infinitepush. This let's it switch the part processing to our infinitepush
798 # infinitepush. This let's it switch the part processing to our infinitepush
799 # code path.
799 # code path.
800 bundler.addparam("infinitepush", "True")
800 bundler.addparam("infinitepush", "True")
801
801
802 scratchparts = bundleparts.getscratchbranchparts(pushop.repo,
802 scratchparts = bundleparts.getscratchbranchparts(pushop.repo,
803 pushop.remote,
803 pushop.remote,
804 pushop.outgoing,
804 pushop.outgoing,
805 pushop.ui,
805 pushop.ui,
806 bookmark)
806 bookmark)
807
807
808 for scratchpart in scratchparts:
808 for scratchpart in scratchparts:
809 bundler.addpart(scratchpart)
809 bundler.addpart(scratchpart)
810
810
811 def handlereply(op):
811 def handlereply(op):
812 # server either succeeds or aborts; no code to read
812 # server either succeeds or aborts; no code to read
813 pushop.cgresult = 1
813 pushop.cgresult = 1
814
814
815 return handlereply
815 return handlereply
816
816
817 bundle2.capabilities[bundleparts.scratchbranchparttype] = ()
817 bundle2.capabilities[bundleparts.scratchbranchparttype] = ()
818
818
819 def _getrevs(bundle, oldnode, force, bookmark):
819 def _getrevs(bundle, oldnode, force, bookmark):
820 'extracts and validates the revs to be imported'
820 'extracts and validates the revs to be imported'
821 revs = [bundle[r] for r in bundle.revs('sort(bundle())')]
821 revs = [bundle[r] for r in bundle.revs('sort(bundle())')]
822
822
823 # new bookmark
823 # new bookmark
824 if oldnode is None:
824 if oldnode is None:
825 return revs
825 return revs
826
826
827 # Fast forward update
827 # Fast forward update
828 if oldnode in bundle and list(bundle.set('bundle() & %s::', oldnode)):
828 if oldnode in bundle and list(bundle.set('bundle() & %s::', oldnode)):
829 return revs
829 return revs
830
830
831 return revs
831 return revs
832
832
833 @contextlib.contextmanager
833 @contextlib.contextmanager
834 def logservicecall(logger, service, **kwargs):
834 def logservicecall(logger, service, **kwargs):
835 start = time.time()
835 start = time.time()
836 logger(service, eventtype='start', **kwargs)
836 logger(service, eventtype='start', **kwargs)
837 try:
837 try:
838 yield
838 yield
839 logger(service, eventtype='success',
839 logger(service, eventtype='success',
840 elapsedms=(time.time() - start) * 1000, **kwargs)
840 elapsedms=(time.time() - start) * 1000, **kwargs)
841 except Exception as e:
841 except Exception as e:
842 logger(service, eventtype='failure',
842 logger(service, eventtype='failure',
843 elapsedms=(time.time() - start) * 1000, errormsg=str(e),
843 elapsedms=(time.time() - start) * 1000, errormsg=str(e),
844 **kwargs)
844 **kwargs)
845 raise
845 raise
846
846
847 def _getorcreateinfinitepushlogger(op):
847 def _getorcreateinfinitepushlogger(op):
848 logger = op.records['infinitepushlogger']
848 logger = op.records['infinitepushlogger']
849 if not logger:
849 if not logger:
850 ui = op.repo.ui
850 ui = op.repo.ui
851 try:
851 try:
852 username = procutil.getuser()
852 username = procutil.getuser()
853 except Exception:
853 except Exception:
854 username = 'unknown'
854 username = 'unknown'
855 # Generate random request id to be able to find all logged entries
855 # Generate random request id to be able to find all logged entries
856 # for the same request. Since requestid is pseudo-generated it may
856 # for the same request. Since requestid is pseudo-generated it may
857 # not be unique, but we assume that (hostname, username, requestid)
857 # not be unique, but we assume that (hostname, username, requestid)
858 # is unique.
858 # is unique.
859 random.seed()
859 random.seed()
860 requestid = random.randint(0, 2000000000)
860 requestid = random.randint(0, 2000000000)
861 hostname = socket.gethostname()
861 hostname = socket.gethostname()
862 logger = functools.partial(ui.log, 'infinitepush', user=username,
862 logger = functools.partial(ui.log, 'infinitepush', user=username,
863 requestid=requestid, hostname=hostname,
863 requestid=requestid, hostname=hostname,
864 reponame=ui.config('infinitepush',
864 reponame=ui.config('infinitepush',
865 'reponame'))
865 'reponame'))
866 op.records.add('infinitepushlogger', logger)
866 op.records.add('infinitepushlogger', logger)
867 else:
867 else:
868 logger = logger[0]
868 logger = logger[0]
869 return logger
869 return logger
870
870
871 def processparts(orig, repo, op, unbundler):
871 def processparts(orig, repo, op, unbundler):
872
873 # make sure we don't wrap processparts in case of `hg unbundle`
874 tr = repo.currenttransaction()
875 if tr:
876 if tr.names[0].startswith('unbundle'):
877 return orig(repo, op, unbundler)
878
872 if unbundler.params.get('infinitepush') != 'True':
879 if unbundler.params.get('infinitepush') != 'True':
873 return orig(repo, op, unbundler)
880 return orig(repo, op, unbundler)
874
881
875 handleallparts = repo.ui.configbool('infinitepush', 'storeallparts')
882 handleallparts = repo.ui.configbool('infinitepush', 'storeallparts')
876
883
877 bundler = bundle2.bundle20(repo.ui)
884 bundler = bundle2.bundle20(repo.ui)
878 cgparams = None
885 cgparams = None
879 with bundle2.partiterator(repo, op, unbundler) as parts:
886 with bundle2.partiterator(repo, op, unbundler) as parts:
880 for part in parts:
887 for part in parts:
881 bundlepart = None
888 bundlepart = None
882 if part.type == 'replycaps':
889 if part.type == 'replycaps':
883 # This configures the current operation to allow reply parts.
890 # This configures the current operation to allow reply parts.
884 bundle2._processpart(op, part)
891 bundle2._processpart(op, part)
885 elif part.type == bundleparts.scratchbranchparttype:
892 elif part.type == bundleparts.scratchbranchparttype:
886 # Scratch branch parts need to be converted to normal
893 # Scratch branch parts need to be converted to normal
887 # changegroup parts, and the extra parameters stored for later
894 # changegroup parts, and the extra parameters stored for later
888 # when we upload to the store. Eventually those parameters will
895 # when we upload to the store. Eventually those parameters will
889 # be put on the actual bundle instead of this part, then we can
896 # be put on the actual bundle instead of this part, then we can
890 # send a vanilla changegroup instead of the scratchbranch part.
897 # send a vanilla changegroup instead of the scratchbranch part.
891 cgversion = part.params.get('cgversion', '01')
898 cgversion = part.params.get('cgversion', '01')
892 bundlepart = bundle2.bundlepart('changegroup', data=part.read())
899 bundlepart = bundle2.bundlepart('changegroup', data=part.read())
893 bundlepart.addparam('version', cgversion)
900 bundlepart.addparam('version', cgversion)
894 cgparams = part.params
901 cgparams = part.params
895
902
896 # If we're not dumping all parts into the new bundle, we need to
903 # If we're not dumping all parts into the new bundle, we need to
897 # alert the future pushkey and phase-heads handler to skip
904 # alert the future pushkey and phase-heads handler to skip
898 # the part.
905 # the part.
899 if not handleallparts:
906 if not handleallparts:
900 op.records.add(scratchbranchparttype + '_skippushkey', True)
907 op.records.add(scratchbranchparttype + '_skippushkey', True)
901 op.records.add(scratchbranchparttype + '_skipphaseheads',
908 op.records.add(scratchbranchparttype + '_skipphaseheads',
902 True)
909 True)
903 else:
910 else:
904 if handleallparts:
911 if handleallparts:
905 # Ideally we would not process any parts, and instead just
912 # Ideally we would not process any parts, and instead just
906 # forward them to the bundle for storage, but since this
913 # forward them to the bundle for storage, but since this
907 # differs from previous behavior, we need to put it behind a
914 # differs from previous behavior, we need to put it behind a
908 # config flag for incremental rollout.
915 # config flag for incremental rollout.
909 bundlepart = bundle2.bundlepart(part.type, data=part.read())
916 bundlepart = bundle2.bundlepart(part.type, data=part.read())
910 for key, value in part.params.iteritems():
917 for key, value in part.params.iteritems():
911 bundlepart.addparam(key, value)
918 bundlepart.addparam(key, value)
912
919
913 # Certain parts require a response
920 # Certain parts require a response
914 if part.type == 'pushkey':
921 if part.type == 'pushkey':
915 if op.reply is not None:
922 if op.reply is not None:
916 rpart = op.reply.newpart('reply:pushkey')
923 rpart = op.reply.newpart('reply:pushkey')
917 rpart.addparam('in-reply-to', str(part.id),
924 rpart.addparam('in-reply-to', str(part.id),
918 mandatory=False)
925 mandatory=False)
919 rpart.addparam('return', '1', mandatory=False)
926 rpart.addparam('return', '1', mandatory=False)
920 else:
927 else:
921 bundle2._processpart(op, part)
928 bundle2._processpart(op, part)
922
929
923 if handleallparts:
930 if handleallparts:
924 op.records.add(part.type, {
931 op.records.add(part.type, {
925 'return': 1,
932 'return': 1,
926 })
933 })
927 if bundlepart:
934 if bundlepart:
928 bundler.addpart(bundlepart)
935 bundler.addpart(bundlepart)
929
936
930 # If commits were sent, store them
937 # If commits were sent, store them
931 if cgparams:
938 if cgparams:
932 buf = util.chunkbuffer(bundler.getchunks())
939 buf = util.chunkbuffer(bundler.getchunks())
933 fd, bundlefile = tempfile.mkstemp()
940 fd, bundlefile = tempfile.mkstemp()
934 try:
941 try:
935 try:
942 try:
936 fp = os.fdopen(fd, 'wb')
943 fp = os.fdopen(fd, 'wb')
937 fp.write(buf.read())
944 fp.write(buf.read())
938 finally:
945 finally:
939 fp.close()
946 fp.close()
940 storebundle(op, cgparams, bundlefile)
947 storebundle(op, cgparams, bundlefile)
941 finally:
948 finally:
942 try:
949 try:
943 os.unlink(bundlefile)
950 os.unlink(bundlefile)
944 except Exception:
951 except Exception:
945 # we would rather see the original exception
952 # we would rather see the original exception
946 pass
953 pass
947
954
948 def storebundle(op, params, bundlefile):
955 def storebundle(op, params, bundlefile):
949 log = _getorcreateinfinitepushlogger(op)
956 log = _getorcreateinfinitepushlogger(op)
950 parthandlerstart = time.time()
957 parthandlerstart = time.time()
951 log(scratchbranchparttype, eventtype='start')
958 log(scratchbranchparttype, eventtype='start')
952 index = op.repo.bundlestore.index
959 index = op.repo.bundlestore.index
953 store = op.repo.bundlestore.store
960 store = op.repo.bundlestore.store
954 op.records.add(scratchbranchparttype + '_skippushkey', True)
961 op.records.add(scratchbranchparttype + '_skippushkey', True)
955
962
956 bundle = None
963 bundle = None
957 try: # guards bundle
964 try: # guards bundle
958 bundlepath = "bundle:%s+%s" % (op.repo.root, bundlefile)
965 bundlepath = "bundle:%s+%s" % (op.repo.root, bundlefile)
959 bundle = hg.repository(op.repo.ui, bundlepath)
966 bundle = hg.repository(op.repo.ui, bundlepath)
960
967
961 bookmark = params.get('bookmark')
968 bookmark = params.get('bookmark')
962 bookprevnode = params.get('bookprevnode', '')
969 bookprevnode = params.get('bookprevnode', '')
963 force = params.get('force')
970 force = params.get('force')
964
971
965 if bookmark:
972 if bookmark:
966 oldnode = index.getnode(bookmark)
973 oldnode = index.getnode(bookmark)
967 else:
974 else:
968 oldnode = None
975 oldnode = None
969 bundleheads = bundle.revs('heads(bundle())')
976 bundleheads = bundle.revs('heads(bundle())')
970 if bookmark and len(bundleheads) > 1:
977 if bookmark and len(bundleheads) > 1:
971 raise error.Abort(
978 raise error.Abort(
972 _('cannot push more than one head to a scratch branch'))
979 _('cannot push more than one head to a scratch branch'))
973
980
974 revs = _getrevs(bundle, oldnode, force, bookmark)
981 revs = _getrevs(bundle, oldnode, force, bookmark)
975
982
976 # Notify the user of what is being pushed
983 # Notify the user of what is being pushed
977 plural = 's' if len(revs) > 1 else ''
984 plural = 's' if len(revs) > 1 else ''
978 op.repo.ui.warn(_("pushing %s commit%s:\n") % (len(revs), plural))
985 op.repo.ui.warn(_("pushing %s commit%s:\n") % (len(revs), plural))
979 maxoutput = 10
986 maxoutput = 10
980 for i in range(0, min(len(revs), maxoutput)):
987 for i in range(0, min(len(revs), maxoutput)):
981 firstline = bundle[revs[i]].description().split('\n')[0][:50]
988 firstline = bundle[revs[i]].description().split('\n')[0][:50]
982 op.repo.ui.warn((" %s %s\n") % (revs[i], firstline))
989 op.repo.ui.warn((" %s %s\n") % (revs[i], firstline))
983
990
984 if len(revs) > maxoutput + 1:
991 if len(revs) > maxoutput + 1:
985 op.repo.ui.warn((" ...\n"))
992 op.repo.ui.warn((" ...\n"))
986 firstline = bundle[revs[-1]].description().split('\n')[0][:50]
993 firstline = bundle[revs[-1]].description().split('\n')[0][:50]
987 op.repo.ui.warn((" %s %s\n") % (revs[-1], firstline))
994 op.repo.ui.warn((" %s %s\n") % (revs[-1], firstline))
988
995
989 nodesctx = [bundle[rev] for rev in revs]
996 nodesctx = [bundle[rev] for rev in revs]
990 inindex = lambda rev: bool(index.getbundle(bundle[rev].hex()))
997 inindex = lambda rev: bool(index.getbundle(bundle[rev].hex()))
991 if bundleheads:
998 if bundleheads:
992 newheadscount = sum(not inindex(rev) for rev in bundleheads)
999 newheadscount = sum(not inindex(rev) for rev in bundleheads)
993 else:
1000 else:
994 newheadscount = 0
1001 newheadscount = 0
995 # If there's a bookmark specified, there should be only one head,
1002 # If there's a bookmark specified, there should be only one head,
996 # so we choose the last node, which will be that head.
1003 # so we choose the last node, which will be that head.
997 # If a bug or malicious client allows there to be a bookmark
1004 # If a bug or malicious client allows there to be a bookmark
998 # with multiple heads, we will place the bookmark on the last head.
1005 # with multiple heads, we will place the bookmark on the last head.
999 bookmarknode = nodesctx[-1].hex() if nodesctx else None
1006 bookmarknode = nodesctx[-1].hex() if nodesctx else None
1000 key = None
1007 key = None
1001 if newheadscount:
1008 if newheadscount:
1002 with open(bundlefile, 'r') as f:
1009 with open(bundlefile, 'r') as f:
1003 bundledata = f.read()
1010 bundledata = f.read()
1004 with logservicecall(log, 'bundlestore',
1011 with logservicecall(log, 'bundlestore',
1005 bundlesize=len(bundledata)):
1012 bundlesize=len(bundledata)):
1006 bundlesizelimit = 100 * 1024 * 1024 # 100 MB
1013 bundlesizelimit = 100 * 1024 * 1024 # 100 MB
1007 if len(bundledata) > bundlesizelimit:
1014 if len(bundledata) > bundlesizelimit:
1008 error_msg = ('bundle is too big: %d bytes. ' +
1015 error_msg = ('bundle is too big: %d bytes. ' +
1009 'max allowed size is 100 MB')
1016 'max allowed size is 100 MB')
1010 raise error.Abort(error_msg % (len(bundledata),))
1017 raise error.Abort(error_msg % (len(bundledata),))
1011 key = store.write(bundledata)
1018 key = store.write(bundledata)
1012
1019
1013 with logservicecall(log, 'index', newheadscount=newheadscount), index:
1020 with logservicecall(log, 'index', newheadscount=newheadscount), index:
1014 if key:
1021 if key:
1015 index.addbundle(key, nodesctx)
1022 index.addbundle(key, nodesctx)
1016 if bookmark:
1023 if bookmark:
1017 index.addbookmark(bookmark, bookmarknode)
1024 index.addbookmark(bookmark, bookmarknode)
1018 _maybeaddpushbackpart(op, bookmark, bookmarknode,
1025 _maybeaddpushbackpart(op, bookmark, bookmarknode,
1019 bookprevnode, params)
1026 bookprevnode, params)
1020 log(scratchbranchparttype, eventtype='success',
1027 log(scratchbranchparttype, eventtype='success',
1021 elapsedms=(time.time() - parthandlerstart) * 1000)
1028 elapsedms=(time.time() - parthandlerstart) * 1000)
1022
1029
1023 except Exception as e:
1030 except Exception as e:
1024 log(scratchbranchparttype, eventtype='failure',
1031 log(scratchbranchparttype, eventtype='failure',
1025 elapsedms=(time.time() - parthandlerstart) * 1000,
1032 elapsedms=(time.time() - parthandlerstart) * 1000,
1026 errormsg=str(e))
1033 errormsg=str(e))
1027 raise
1034 raise
1028 finally:
1035 finally:
1029 if bundle:
1036 if bundle:
1030 bundle.close()
1037 bundle.close()
1031
1038
1032 @bundle2.parthandler(scratchbranchparttype,
1039 @bundle2.parthandler(scratchbranchparttype,
1033 ('bookmark', 'bookprevnode', 'force',
1040 ('bookmark', 'bookprevnode', 'force',
1034 'pushbackbookmarks', 'cgversion'))
1041 'pushbackbookmarks', 'cgversion'))
1035 def bundle2scratchbranch(op, part):
1042 def bundle2scratchbranch(op, part):
1036 '''unbundle a bundle2 part containing a changegroup to store'''
1043 '''unbundle a bundle2 part containing a changegroup to store'''
1037
1044
1038 bundler = bundle2.bundle20(op.repo.ui)
1045 bundler = bundle2.bundle20(op.repo.ui)
1039 cgversion = part.params.get('cgversion', '01')
1046 cgversion = part.params.get('cgversion', '01')
1040 cgpart = bundle2.bundlepart('changegroup', data=part.read())
1047 cgpart = bundle2.bundlepart('changegroup', data=part.read())
1041 cgpart.addparam('version', cgversion)
1048 cgpart.addparam('version', cgversion)
1042 bundler.addpart(cgpart)
1049 bundler.addpart(cgpart)
1043 buf = util.chunkbuffer(bundler.getchunks())
1050 buf = util.chunkbuffer(bundler.getchunks())
1044
1051
1045 fd, bundlefile = tempfile.mkstemp()
1052 fd, bundlefile = tempfile.mkstemp()
1046 try:
1053 try:
1047 try:
1054 try:
1048 fp = os.fdopen(fd, 'wb')
1055 fp = os.fdopen(fd, 'wb')
1049 fp.write(buf.read())
1056 fp.write(buf.read())
1050 finally:
1057 finally:
1051 fp.close()
1058 fp.close()
1052 storebundle(op, part.params, bundlefile)
1059 storebundle(op, part.params, bundlefile)
1053 finally:
1060 finally:
1054 try:
1061 try:
1055 os.unlink(bundlefile)
1062 os.unlink(bundlefile)
1056 except OSError as e:
1063 except OSError as e:
1057 if e.errno != errno.ENOENT:
1064 if e.errno != errno.ENOENT:
1058 raise
1065 raise
1059
1066
1060 return 1
1067 return 1
1061
1068
1062 def _maybeaddpushbackpart(op, bookmark, newnode, oldnode, params):
1069 def _maybeaddpushbackpart(op, bookmark, newnode, oldnode, params):
1063 if params.get('pushbackbookmarks'):
1070 if params.get('pushbackbookmarks'):
1064 if op.reply and 'pushback' in op.reply.capabilities:
1071 if op.reply and 'pushback' in op.reply.capabilities:
1065 params = {
1072 params = {
1066 'namespace': 'bookmarks',
1073 'namespace': 'bookmarks',
1067 'key': bookmark,
1074 'key': bookmark,
1068 'new': newnode,
1075 'new': newnode,
1069 'old': oldnode,
1076 'old': oldnode,
1070 }
1077 }
1071 op.reply.newpart('pushkey', mandatoryparams=params.iteritems())
1078 op.reply.newpart('pushkey', mandatoryparams=params.iteritems())
1072
1079
1073 def bundle2pushkey(orig, op, part):
1080 def bundle2pushkey(orig, op, part):
1074 '''Wrapper of bundle2.handlepushkey()
1081 '''Wrapper of bundle2.handlepushkey()
1075
1082
1076 The only goal is to skip calling the original function if flag is set.
1083 The only goal is to skip calling the original function if flag is set.
1077 It's set if infinitepush push is happening.
1084 It's set if infinitepush push is happening.
1078 '''
1085 '''
1079 if op.records[scratchbranchparttype + '_skippushkey']:
1086 if op.records[scratchbranchparttype + '_skippushkey']:
1080 if op.reply is not None:
1087 if op.reply is not None:
1081 rpart = op.reply.newpart('reply:pushkey')
1088 rpart = op.reply.newpart('reply:pushkey')
1082 rpart.addparam('in-reply-to', str(part.id), mandatory=False)
1089 rpart.addparam('in-reply-to', str(part.id), mandatory=False)
1083 rpart.addparam('return', '1', mandatory=False)
1090 rpart.addparam('return', '1', mandatory=False)
1084 return 1
1091 return 1
1085
1092
1086 return orig(op, part)
1093 return orig(op, part)
1087
1094
1088 def bundle2handlephases(orig, op, part):
1095 def bundle2handlephases(orig, op, part):
1089 '''Wrapper of bundle2.handlephases()
1096 '''Wrapper of bundle2.handlephases()
1090
1097
1091 The only goal is to skip calling the original function if flag is set.
1098 The only goal is to skip calling the original function if flag is set.
1092 It's set if infinitepush push is happening.
1099 It's set if infinitepush push is happening.
1093 '''
1100 '''
1094
1101
1095 if op.records[scratchbranchparttype + '_skipphaseheads']:
1102 if op.records[scratchbranchparttype + '_skipphaseheads']:
1096 return
1103 return
1097
1104
1098 return orig(op, part)
1105 return orig(op, part)
1099
1106
1100 def _asyncsavemetadata(root, nodes):
1107 def _asyncsavemetadata(root, nodes):
1101 '''starts a separate process that fills metadata for the nodes
1108 '''starts a separate process that fills metadata for the nodes
1102
1109
1103 This function creates a separate process and doesn't wait for it's
1110 This function creates a separate process and doesn't wait for it's
1104 completion. This was done to avoid slowing down pushes
1111 completion. This was done to avoid slowing down pushes
1105 '''
1112 '''
1106
1113
1107 maxnodes = 50
1114 maxnodes = 50
1108 if len(nodes) > maxnodes:
1115 if len(nodes) > maxnodes:
1109 return
1116 return
1110 nodesargs = []
1117 nodesargs = []
1111 for node in nodes:
1118 for node in nodes:
1112 nodesargs.append('--node')
1119 nodesargs.append('--node')
1113 nodesargs.append(node)
1120 nodesargs.append(node)
1114 with open(os.devnull, 'w+b') as devnull:
1121 with open(os.devnull, 'w+b') as devnull:
1115 cmdline = [util.hgexecutable(), 'debugfillinfinitepushmetadata',
1122 cmdline = [util.hgexecutable(), 'debugfillinfinitepushmetadata',
1116 '-R', root] + nodesargs
1123 '-R', root] + nodesargs
1117 # Process will run in background. We don't care about the return code
1124 # Process will run in background. We don't care about the return code
1118 subprocess.Popen(cmdline, close_fds=True, shell=False,
1125 subprocess.Popen(cmdline, close_fds=True, shell=False,
1119 stdin=devnull, stdout=devnull, stderr=devnull)
1126 stdin=devnull, stdout=devnull, stderr=devnull)
General Comments 0
You need to be logged in to leave comments. Login now