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