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