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