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