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