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