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