##// END OF EJS Templates
copytrace: replace experimental.disablecopytrace config with copytrace (BC)...
Pulkit Goyal -
r34079:26531db4 default
parent child Browse files
Show More
@@ -1,601 +1,601 b''
1 # configitems.py - centralized declaration of configuration option
1 # configitems.py - centralized declaration of configuration option
2 #
2 #
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
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
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import functools
10 import functools
11
11
12 from . import (
12 from . import (
13 error,
13 error,
14 )
14 )
15
15
16 def loadconfigtable(ui, extname, configtable):
16 def loadconfigtable(ui, extname, configtable):
17 """update config item known to the ui with the extension ones"""
17 """update config item known to the ui with the extension ones"""
18 for section, items in configtable.items():
18 for section, items in configtable.items():
19 knownitems = ui._knownconfig.setdefault(section, {})
19 knownitems = ui._knownconfig.setdefault(section, {})
20 knownkeys = set(knownitems)
20 knownkeys = set(knownitems)
21 newkeys = set(items)
21 newkeys = set(items)
22 for key in sorted(knownkeys & newkeys):
22 for key in sorted(knownkeys & newkeys):
23 msg = "extension '%s' overwrite config item '%s.%s'"
23 msg = "extension '%s' overwrite config item '%s.%s'"
24 msg %= (extname, section, key)
24 msg %= (extname, section, key)
25 ui.develwarn(msg, config='warn-config')
25 ui.develwarn(msg, config='warn-config')
26
26
27 knownitems.update(items)
27 knownitems.update(items)
28
28
29 class configitem(object):
29 class configitem(object):
30 """represent a known config item
30 """represent a known config item
31
31
32 :section: the official config section where to find this item,
32 :section: the official config section where to find this item,
33 :name: the official name within the section,
33 :name: the official name within the section,
34 :default: default value for this item,
34 :default: default value for this item,
35 :alias: optional list of tuples as alternatives.
35 :alias: optional list of tuples as alternatives.
36 """
36 """
37
37
38 def __init__(self, section, name, default=None, alias=()):
38 def __init__(self, section, name, default=None, alias=()):
39 self.section = section
39 self.section = section
40 self.name = name
40 self.name = name
41 self.default = default
41 self.default = default
42 self.alias = list(alias)
42 self.alias = list(alias)
43
43
44 coreitems = {}
44 coreitems = {}
45
45
46 def _register(configtable, *args, **kwargs):
46 def _register(configtable, *args, **kwargs):
47 item = configitem(*args, **kwargs)
47 item = configitem(*args, **kwargs)
48 section = configtable.setdefault(item.section, {})
48 section = configtable.setdefault(item.section, {})
49 if item.name in section:
49 if item.name in section:
50 msg = "duplicated config item registration for '%s.%s'"
50 msg = "duplicated config item registration for '%s.%s'"
51 raise error.ProgrammingError(msg % (item.section, item.name))
51 raise error.ProgrammingError(msg % (item.section, item.name))
52 section[item.name] = item
52 section[item.name] = item
53
53
54 # special value for case where the default is derived from other values
54 # special value for case where the default is derived from other values
55 dynamicdefault = object()
55 dynamicdefault = object()
56
56
57 # Registering actual config items
57 # Registering actual config items
58
58
59 def getitemregister(configtable):
59 def getitemregister(configtable):
60 return functools.partial(_register, configtable)
60 return functools.partial(_register, configtable)
61
61
62 coreconfigitem = getitemregister(coreitems)
62 coreconfigitem = getitemregister(coreitems)
63
63
64 coreconfigitem('auth', 'cookiefile',
64 coreconfigitem('auth', 'cookiefile',
65 default=None,
65 default=None,
66 )
66 )
67 # bookmarks.pushing: internal hack for discovery
67 # bookmarks.pushing: internal hack for discovery
68 coreconfigitem('bookmarks', 'pushing',
68 coreconfigitem('bookmarks', 'pushing',
69 default=list,
69 default=list,
70 )
70 )
71 # bundle.mainreporoot: internal hack for bundlerepo
71 # bundle.mainreporoot: internal hack for bundlerepo
72 coreconfigitem('bundle', 'mainreporoot',
72 coreconfigitem('bundle', 'mainreporoot',
73 default='',
73 default='',
74 )
74 )
75 # bundle.reorder: experimental config
75 # bundle.reorder: experimental config
76 coreconfigitem('bundle', 'reorder',
76 coreconfigitem('bundle', 'reorder',
77 default='auto',
77 default='auto',
78 )
78 )
79 coreconfigitem('censor', 'policy',
79 coreconfigitem('censor', 'policy',
80 default='abort',
80 default='abort',
81 )
81 )
82 coreconfigitem('chgserver', 'idletimeout',
82 coreconfigitem('chgserver', 'idletimeout',
83 default=3600,
83 default=3600,
84 )
84 )
85 coreconfigitem('chgserver', 'skiphash',
85 coreconfigitem('chgserver', 'skiphash',
86 default=False,
86 default=False,
87 )
87 )
88 coreconfigitem('cmdserver', 'log',
88 coreconfigitem('cmdserver', 'log',
89 default=None,
89 default=None,
90 )
90 )
91 coreconfigitem('color', 'mode',
91 coreconfigitem('color', 'mode',
92 default='auto',
92 default='auto',
93 )
93 )
94 coreconfigitem('color', 'pagermode',
94 coreconfigitem('color', 'pagermode',
95 default=dynamicdefault,
95 default=dynamicdefault,
96 )
96 )
97 coreconfigitem('commands', 'status.relative',
97 coreconfigitem('commands', 'status.relative',
98 default=False,
98 default=False,
99 )
99 )
100 coreconfigitem('commands', 'status.skipstates',
100 coreconfigitem('commands', 'status.skipstates',
101 default=[],
101 default=[],
102 )
102 )
103 coreconfigitem('commands', 'status.verbose',
103 coreconfigitem('commands', 'status.verbose',
104 default=False,
104 default=False,
105 )
105 )
106 coreconfigitem('commands', 'update.requiredest',
106 coreconfigitem('commands', 'update.requiredest',
107 default=False,
107 default=False,
108 )
108 )
109 coreconfigitem('devel', 'all-warnings',
109 coreconfigitem('devel', 'all-warnings',
110 default=False,
110 default=False,
111 )
111 )
112 coreconfigitem('devel', 'bundle2.debug',
112 coreconfigitem('devel', 'bundle2.debug',
113 default=False,
113 default=False,
114 )
114 )
115 coreconfigitem('devel', 'check-locks',
115 coreconfigitem('devel', 'check-locks',
116 default=False,
116 default=False,
117 )
117 )
118 coreconfigitem('devel', 'check-relroot',
118 coreconfigitem('devel', 'check-relroot',
119 default=False,
119 default=False,
120 )
120 )
121 coreconfigitem('devel', 'default-date',
121 coreconfigitem('devel', 'default-date',
122 default=None,
122 default=None,
123 )
123 )
124 coreconfigitem('devel', 'deprec-warn',
124 coreconfigitem('devel', 'deprec-warn',
125 default=False,
125 default=False,
126 )
126 )
127 coreconfigitem('devel', 'disableloaddefaultcerts',
127 coreconfigitem('devel', 'disableloaddefaultcerts',
128 default=False,
128 default=False,
129 )
129 )
130 coreconfigitem('devel', 'legacy.exchange',
130 coreconfigitem('devel', 'legacy.exchange',
131 default=list,
131 default=list,
132 )
132 )
133 coreconfigitem('devel', 'servercafile',
133 coreconfigitem('devel', 'servercafile',
134 default='',
134 default='',
135 )
135 )
136 coreconfigitem('devel', 'serverexactprotocol',
136 coreconfigitem('devel', 'serverexactprotocol',
137 default='',
137 default='',
138 )
138 )
139 coreconfigitem('devel', 'serverrequirecert',
139 coreconfigitem('devel', 'serverrequirecert',
140 default=False,
140 default=False,
141 )
141 )
142 coreconfigitem('devel', 'strip-obsmarkers',
142 coreconfigitem('devel', 'strip-obsmarkers',
143 default=True,
143 default=True,
144 )
144 )
145 coreconfigitem('email', 'charsets',
145 coreconfigitem('email', 'charsets',
146 default=list,
146 default=list,
147 )
147 )
148 coreconfigitem('email', 'method',
148 coreconfigitem('email', 'method',
149 default='smtp',
149 default='smtp',
150 )
150 )
151 coreconfigitem('experimental', 'bundle-phases',
151 coreconfigitem('experimental', 'bundle-phases',
152 default=False,
152 default=False,
153 )
153 )
154 coreconfigitem('experimental', 'bundle2-advertise',
154 coreconfigitem('experimental', 'bundle2-advertise',
155 default=True,
155 default=True,
156 )
156 )
157 coreconfigitem('experimental', 'bundle2-output-capture',
157 coreconfigitem('experimental', 'bundle2-output-capture',
158 default=False,
158 default=False,
159 )
159 )
160 coreconfigitem('experimental', 'bundle2.pushback',
160 coreconfigitem('experimental', 'bundle2.pushback',
161 default=False,
161 default=False,
162 )
162 )
163 coreconfigitem('experimental', 'bundle2lazylocking',
163 coreconfigitem('experimental', 'bundle2lazylocking',
164 default=False,
164 default=False,
165 )
165 )
166 coreconfigitem('experimental', 'bundlecomplevel',
166 coreconfigitem('experimental', 'bundlecomplevel',
167 default=None,
167 default=None,
168 )
168 )
169 coreconfigitem('experimental', 'changegroup3',
169 coreconfigitem('experimental', 'changegroup3',
170 default=False,
170 default=False,
171 )
171 )
172 coreconfigitem('experimental', 'clientcompressionengines',
172 coreconfigitem('experimental', 'clientcompressionengines',
173 default=list,
173 default=list,
174 )
174 )
175 coreconfigitem('experimental', 'copytrace',
176 default='on',
177 )
175 coreconfigitem('experimental', 'crecordtest',
178 coreconfigitem('experimental', 'crecordtest',
176 default=None,
179 default=None,
177 )
180 )
178 coreconfigitem('experimental', 'disablecopytrace',
179 default=False,
180 )
181 coreconfigitem('experimental', 'editortmpinhg',
181 coreconfigitem('experimental', 'editortmpinhg',
182 default=False,
182 default=False,
183 )
183 )
184 coreconfigitem('experimental', 'stabilization',
184 coreconfigitem('experimental', 'stabilization',
185 default=list,
185 default=list,
186 alias=[('experimental', 'evolution')],
186 alias=[('experimental', 'evolution')],
187 )
187 )
188 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
188 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
189 default=False,
189 default=False,
190 alias=[('experimental', 'evolution.bundle-obsmarker')],
190 alias=[('experimental', 'evolution.bundle-obsmarker')],
191 )
191 )
192 coreconfigitem('experimental', 'stabilization.track-operation',
192 coreconfigitem('experimental', 'stabilization.track-operation',
193 default=False,
193 default=False,
194 alias=[('experimental', 'evolution.track-operation')]
194 alias=[('experimental', 'evolution.track-operation')]
195 )
195 )
196 coreconfigitem('experimental', 'exportableenviron',
196 coreconfigitem('experimental', 'exportableenviron',
197 default=list,
197 default=list,
198 )
198 )
199 coreconfigitem('experimental', 'extendedheader.index',
199 coreconfigitem('experimental', 'extendedheader.index',
200 default=None,
200 default=None,
201 )
201 )
202 coreconfigitem('experimental', 'extendedheader.similarity',
202 coreconfigitem('experimental', 'extendedheader.similarity',
203 default=False,
203 default=False,
204 )
204 )
205 coreconfigitem('experimental', 'format.compression',
205 coreconfigitem('experimental', 'format.compression',
206 default='zlib',
206 default='zlib',
207 )
207 )
208 coreconfigitem('experimental', 'graphshorten',
208 coreconfigitem('experimental', 'graphshorten',
209 default=False,
209 default=False,
210 )
210 )
211 coreconfigitem('experimental', 'hook-track-tags',
211 coreconfigitem('experimental', 'hook-track-tags',
212 default=False,
212 default=False,
213 )
213 )
214 coreconfigitem('experimental', 'httppostargs',
214 coreconfigitem('experimental', 'httppostargs',
215 default=False,
215 default=False,
216 )
216 )
217 coreconfigitem('experimental', 'manifestv2',
217 coreconfigitem('experimental', 'manifestv2',
218 default=False,
218 default=False,
219 )
219 )
220 coreconfigitem('experimental', 'mergedriver',
220 coreconfigitem('experimental', 'mergedriver',
221 default=None,
221 default=None,
222 )
222 )
223 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
223 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
224 default=False,
224 default=False,
225 )
225 )
226 coreconfigitem('experimental', 'rebase.multidest',
226 coreconfigitem('experimental', 'rebase.multidest',
227 default=False,
227 default=False,
228 )
228 )
229 coreconfigitem('experimental', 'revertalternateinteractivemode',
229 coreconfigitem('experimental', 'revertalternateinteractivemode',
230 default=True,
230 default=True,
231 )
231 )
232 coreconfigitem('experimental', 'revlogv2',
232 coreconfigitem('experimental', 'revlogv2',
233 default=None,
233 default=None,
234 )
234 )
235 coreconfigitem('experimental', 'spacemovesdown',
235 coreconfigitem('experimental', 'spacemovesdown',
236 default=False,
236 default=False,
237 )
237 )
238 coreconfigitem('experimental', 'treemanifest',
238 coreconfigitem('experimental', 'treemanifest',
239 default=False,
239 default=False,
240 )
240 )
241 coreconfigitem('experimental', 'updatecheck',
241 coreconfigitem('experimental', 'updatecheck',
242 default=None,
242 default=None,
243 )
243 )
244 coreconfigitem('format', 'aggressivemergedeltas',
244 coreconfigitem('format', 'aggressivemergedeltas',
245 default=False,
245 default=False,
246 )
246 )
247 coreconfigitem('format', 'chunkcachesize',
247 coreconfigitem('format', 'chunkcachesize',
248 default=None,
248 default=None,
249 )
249 )
250 coreconfigitem('format', 'dotencode',
250 coreconfigitem('format', 'dotencode',
251 default=True,
251 default=True,
252 )
252 )
253 coreconfigitem('format', 'generaldelta',
253 coreconfigitem('format', 'generaldelta',
254 default=False,
254 default=False,
255 )
255 )
256 coreconfigitem('format', 'manifestcachesize',
256 coreconfigitem('format', 'manifestcachesize',
257 default=None,
257 default=None,
258 )
258 )
259 coreconfigitem('format', 'maxchainlen',
259 coreconfigitem('format', 'maxchainlen',
260 default=None,
260 default=None,
261 )
261 )
262 coreconfigitem('format', 'obsstore-version',
262 coreconfigitem('format', 'obsstore-version',
263 default=None,
263 default=None,
264 )
264 )
265 coreconfigitem('format', 'usefncache',
265 coreconfigitem('format', 'usefncache',
266 default=True,
266 default=True,
267 )
267 )
268 coreconfigitem('format', 'usegeneraldelta',
268 coreconfigitem('format', 'usegeneraldelta',
269 default=True,
269 default=True,
270 )
270 )
271 coreconfigitem('format', 'usestore',
271 coreconfigitem('format', 'usestore',
272 default=True,
272 default=True,
273 )
273 )
274 coreconfigitem('hostsecurity', 'ciphers',
274 coreconfigitem('hostsecurity', 'ciphers',
275 default=None,
275 default=None,
276 )
276 )
277 coreconfigitem('hostsecurity', 'disabletls10warning',
277 coreconfigitem('hostsecurity', 'disabletls10warning',
278 default=False,
278 default=False,
279 )
279 )
280 coreconfigitem('http_proxy', 'always',
280 coreconfigitem('http_proxy', 'always',
281 default=False,
281 default=False,
282 )
282 )
283 coreconfigitem('http_proxy', 'host',
283 coreconfigitem('http_proxy', 'host',
284 default=None,
284 default=None,
285 )
285 )
286 coreconfigitem('http_proxy', 'no',
286 coreconfigitem('http_proxy', 'no',
287 default=list,
287 default=list,
288 )
288 )
289 coreconfigitem('http_proxy', 'passwd',
289 coreconfigitem('http_proxy', 'passwd',
290 default=None,
290 default=None,
291 )
291 )
292 coreconfigitem('http_proxy', 'user',
292 coreconfigitem('http_proxy', 'user',
293 default=None,
293 default=None,
294 )
294 )
295 coreconfigitem('merge', 'followcopies',
295 coreconfigitem('merge', 'followcopies',
296 default=True,
296 default=True,
297 )
297 )
298 coreconfigitem('pager', 'ignore',
298 coreconfigitem('pager', 'ignore',
299 default=list,
299 default=list,
300 )
300 )
301 coreconfigitem('patch', 'eol',
301 coreconfigitem('patch', 'eol',
302 default='strict',
302 default='strict',
303 )
303 )
304 coreconfigitem('patch', 'fuzz',
304 coreconfigitem('patch', 'fuzz',
305 default=2,
305 default=2,
306 )
306 )
307 coreconfigitem('paths', 'default',
307 coreconfigitem('paths', 'default',
308 default=None,
308 default=None,
309 )
309 )
310 coreconfigitem('paths', 'default-push',
310 coreconfigitem('paths', 'default-push',
311 default=None,
311 default=None,
312 )
312 )
313 coreconfigitem('phases', 'checksubrepos',
313 coreconfigitem('phases', 'checksubrepos',
314 default='follow',
314 default='follow',
315 )
315 )
316 coreconfigitem('phases', 'publish',
316 coreconfigitem('phases', 'publish',
317 default=True,
317 default=True,
318 )
318 )
319 coreconfigitem('profiling', 'enabled',
319 coreconfigitem('profiling', 'enabled',
320 default=False,
320 default=False,
321 )
321 )
322 coreconfigitem('profiling', 'format',
322 coreconfigitem('profiling', 'format',
323 default='text',
323 default='text',
324 )
324 )
325 coreconfigitem('profiling', 'freq',
325 coreconfigitem('profiling', 'freq',
326 default=1000,
326 default=1000,
327 )
327 )
328 coreconfigitem('profiling', 'limit',
328 coreconfigitem('profiling', 'limit',
329 default=30,
329 default=30,
330 )
330 )
331 coreconfigitem('profiling', 'nested',
331 coreconfigitem('profiling', 'nested',
332 default=0,
332 default=0,
333 )
333 )
334 coreconfigitem('profiling', 'sort',
334 coreconfigitem('profiling', 'sort',
335 default='inlinetime',
335 default='inlinetime',
336 )
336 )
337 coreconfigitem('profiling', 'statformat',
337 coreconfigitem('profiling', 'statformat',
338 default='hotpath',
338 default='hotpath',
339 )
339 )
340 coreconfigitem('progress', 'assume-tty',
340 coreconfigitem('progress', 'assume-tty',
341 default=False,
341 default=False,
342 )
342 )
343 coreconfigitem('progress', 'changedelay',
343 coreconfigitem('progress', 'changedelay',
344 default=1,
344 default=1,
345 )
345 )
346 coreconfigitem('progress', 'clear-complete',
346 coreconfigitem('progress', 'clear-complete',
347 default=True,
347 default=True,
348 )
348 )
349 coreconfigitem('progress', 'debug',
349 coreconfigitem('progress', 'debug',
350 default=False,
350 default=False,
351 )
351 )
352 coreconfigitem('progress', 'delay',
352 coreconfigitem('progress', 'delay',
353 default=3,
353 default=3,
354 )
354 )
355 coreconfigitem('progress', 'disable',
355 coreconfigitem('progress', 'disable',
356 default=False,
356 default=False,
357 )
357 )
358 coreconfigitem('progress', 'estimate',
358 coreconfigitem('progress', 'estimate',
359 default=2,
359 default=2,
360 )
360 )
361 coreconfigitem('progress', 'refresh',
361 coreconfigitem('progress', 'refresh',
362 default=0.1,
362 default=0.1,
363 )
363 )
364 coreconfigitem('progress', 'width',
364 coreconfigitem('progress', 'width',
365 default=dynamicdefault,
365 default=dynamicdefault,
366 )
366 )
367 coreconfigitem('push', 'pushvars.server',
367 coreconfigitem('push', 'pushvars.server',
368 default=False,
368 default=False,
369 )
369 )
370 coreconfigitem('server', 'bundle1',
370 coreconfigitem('server', 'bundle1',
371 default=True,
371 default=True,
372 )
372 )
373 coreconfigitem('server', 'bundle1gd',
373 coreconfigitem('server', 'bundle1gd',
374 default=None,
374 default=None,
375 )
375 )
376 coreconfigitem('server', 'compressionengines',
376 coreconfigitem('server', 'compressionengines',
377 default=list,
377 default=list,
378 )
378 )
379 coreconfigitem('server', 'concurrent-push-mode',
379 coreconfigitem('server', 'concurrent-push-mode',
380 default='strict',
380 default='strict',
381 )
381 )
382 coreconfigitem('server', 'disablefullbundle',
382 coreconfigitem('server', 'disablefullbundle',
383 default=False,
383 default=False,
384 )
384 )
385 coreconfigitem('server', 'maxhttpheaderlen',
385 coreconfigitem('server', 'maxhttpheaderlen',
386 default=1024,
386 default=1024,
387 )
387 )
388 coreconfigitem('server', 'preferuncompressed',
388 coreconfigitem('server', 'preferuncompressed',
389 default=False,
389 default=False,
390 )
390 )
391 coreconfigitem('server', 'uncompressed',
391 coreconfigitem('server', 'uncompressed',
392 default=True,
392 default=True,
393 )
393 )
394 coreconfigitem('server', 'uncompressedallowsecret',
394 coreconfigitem('server', 'uncompressedallowsecret',
395 default=False,
395 default=False,
396 )
396 )
397 coreconfigitem('server', 'validate',
397 coreconfigitem('server', 'validate',
398 default=False,
398 default=False,
399 )
399 )
400 coreconfigitem('server', 'zliblevel',
400 coreconfigitem('server', 'zliblevel',
401 default=-1,
401 default=-1,
402 )
402 )
403 coreconfigitem('smtp', 'host',
403 coreconfigitem('smtp', 'host',
404 default=None,
404 default=None,
405 )
405 )
406 coreconfigitem('smtp', 'local_hostname',
406 coreconfigitem('smtp', 'local_hostname',
407 default=None,
407 default=None,
408 )
408 )
409 coreconfigitem('smtp', 'password',
409 coreconfigitem('smtp', 'password',
410 default=None,
410 default=None,
411 )
411 )
412 coreconfigitem('smtp', 'tls',
412 coreconfigitem('smtp', 'tls',
413 default='none',
413 default='none',
414 )
414 )
415 coreconfigitem('smtp', 'username',
415 coreconfigitem('smtp', 'username',
416 default=None,
416 default=None,
417 )
417 )
418 coreconfigitem('sparse', 'missingwarning',
418 coreconfigitem('sparse', 'missingwarning',
419 default=True,
419 default=True,
420 )
420 )
421 coreconfigitem('trusted', 'groups',
421 coreconfigitem('trusted', 'groups',
422 default=list,
422 default=list,
423 )
423 )
424 coreconfigitem('trusted', 'users',
424 coreconfigitem('trusted', 'users',
425 default=list,
425 default=list,
426 )
426 )
427 coreconfigitem('ui', '_usedassubrepo',
427 coreconfigitem('ui', '_usedassubrepo',
428 default=False,
428 default=False,
429 )
429 )
430 coreconfigitem('ui', 'allowemptycommit',
430 coreconfigitem('ui', 'allowemptycommit',
431 default=False,
431 default=False,
432 )
432 )
433 coreconfigitem('ui', 'archivemeta',
433 coreconfigitem('ui', 'archivemeta',
434 default=True,
434 default=True,
435 )
435 )
436 coreconfigitem('ui', 'askusername',
436 coreconfigitem('ui', 'askusername',
437 default=False,
437 default=False,
438 )
438 )
439 coreconfigitem('ui', 'clonebundlefallback',
439 coreconfigitem('ui', 'clonebundlefallback',
440 default=False,
440 default=False,
441 )
441 )
442 coreconfigitem('ui', 'clonebundleprefers',
442 coreconfigitem('ui', 'clonebundleprefers',
443 default=list,
443 default=list,
444 )
444 )
445 coreconfigitem('ui', 'clonebundles',
445 coreconfigitem('ui', 'clonebundles',
446 default=True,
446 default=True,
447 )
447 )
448 coreconfigitem('ui', 'color',
448 coreconfigitem('ui', 'color',
449 default='auto',
449 default='auto',
450 )
450 )
451 coreconfigitem('ui', 'commitsubrepos',
451 coreconfigitem('ui', 'commitsubrepos',
452 default=False,
452 default=False,
453 )
453 )
454 coreconfigitem('ui', 'debug',
454 coreconfigitem('ui', 'debug',
455 default=False,
455 default=False,
456 )
456 )
457 coreconfigitem('ui', 'debugger',
457 coreconfigitem('ui', 'debugger',
458 default=None,
458 default=None,
459 )
459 )
460 coreconfigitem('ui', 'fallbackencoding',
460 coreconfigitem('ui', 'fallbackencoding',
461 default=None,
461 default=None,
462 )
462 )
463 coreconfigitem('ui', 'forcecwd',
463 coreconfigitem('ui', 'forcecwd',
464 default=None,
464 default=None,
465 )
465 )
466 coreconfigitem('ui', 'forcemerge',
466 coreconfigitem('ui', 'forcemerge',
467 default=None,
467 default=None,
468 )
468 )
469 coreconfigitem('ui', 'formatdebug',
469 coreconfigitem('ui', 'formatdebug',
470 default=False,
470 default=False,
471 )
471 )
472 coreconfigitem('ui', 'formatjson',
472 coreconfigitem('ui', 'formatjson',
473 default=False,
473 default=False,
474 )
474 )
475 coreconfigitem('ui', 'formatted',
475 coreconfigitem('ui', 'formatted',
476 default=None,
476 default=None,
477 )
477 )
478 coreconfigitem('ui', 'graphnodetemplate',
478 coreconfigitem('ui', 'graphnodetemplate',
479 default=None,
479 default=None,
480 )
480 )
481 coreconfigitem('ui', 'http2debuglevel',
481 coreconfigitem('ui', 'http2debuglevel',
482 default=None,
482 default=None,
483 )
483 )
484 coreconfigitem('ui', 'interactive',
484 coreconfigitem('ui', 'interactive',
485 default=None,
485 default=None,
486 )
486 )
487 coreconfigitem('ui', 'interface',
487 coreconfigitem('ui', 'interface',
488 default=None,
488 default=None,
489 )
489 )
490 coreconfigitem('ui', 'logblockedtimes',
490 coreconfigitem('ui', 'logblockedtimes',
491 default=False,
491 default=False,
492 )
492 )
493 coreconfigitem('ui', 'logtemplate',
493 coreconfigitem('ui', 'logtemplate',
494 default=None,
494 default=None,
495 )
495 )
496 coreconfigitem('ui', 'merge',
496 coreconfigitem('ui', 'merge',
497 default=None,
497 default=None,
498 )
498 )
499 coreconfigitem('ui', 'mergemarkers',
499 coreconfigitem('ui', 'mergemarkers',
500 default='basic',
500 default='basic',
501 )
501 )
502 coreconfigitem('ui', 'mergemarkertemplate',
502 coreconfigitem('ui', 'mergemarkertemplate',
503 default=('{node|short} '
503 default=('{node|short} '
504 '{ifeq(tags, "tip", "", '
504 '{ifeq(tags, "tip", "", '
505 'ifeq(tags, "", "", "{tags} "))}'
505 'ifeq(tags, "", "", "{tags} "))}'
506 '{if(bookmarks, "{bookmarks} ")}'
506 '{if(bookmarks, "{bookmarks} ")}'
507 '{ifeq(branch, "default", "", "{branch} ")}'
507 '{ifeq(branch, "default", "", "{branch} ")}'
508 '- {author|user}: {desc|firstline}')
508 '- {author|user}: {desc|firstline}')
509 )
509 )
510 coreconfigitem('ui', 'nontty',
510 coreconfigitem('ui', 'nontty',
511 default=False,
511 default=False,
512 )
512 )
513 coreconfigitem('ui', 'origbackuppath',
513 coreconfigitem('ui', 'origbackuppath',
514 default=None,
514 default=None,
515 )
515 )
516 coreconfigitem('ui', 'paginate',
516 coreconfigitem('ui', 'paginate',
517 default=True,
517 default=True,
518 )
518 )
519 coreconfigitem('ui', 'patch',
519 coreconfigitem('ui', 'patch',
520 default=None,
520 default=None,
521 )
521 )
522 coreconfigitem('ui', 'portablefilenames',
522 coreconfigitem('ui', 'portablefilenames',
523 default='warn',
523 default='warn',
524 )
524 )
525 coreconfigitem('ui', 'promptecho',
525 coreconfigitem('ui', 'promptecho',
526 default=False,
526 default=False,
527 )
527 )
528 coreconfigitem('ui', 'quiet',
528 coreconfigitem('ui', 'quiet',
529 default=False,
529 default=False,
530 )
530 )
531 coreconfigitem('ui', 'quietbookmarkmove',
531 coreconfigitem('ui', 'quietbookmarkmove',
532 default=False,
532 default=False,
533 )
533 )
534 coreconfigitem('ui', 'remotecmd',
534 coreconfigitem('ui', 'remotecmd',
535 default='hg',
535 default='hg',
536 )
536 )
537 coreconfigitem('ui', 'report_untrusted',
537 coreconfigitem('ui', 'report_untrusted',
538 default=True,
538 default=True,
539 )
539 )
540 coreconfigitem('ui', 'rollback',
540 coreconfigitem('ui', 'rollback',
541 default=True,
541 default=True,
542 )
542 )
543 coreconfigitem('ui', 'slash',
543 coreconfigitem('ui', 'slash',
544 default=False,
544 default=False,
545 )
545 )
546 coreconfigitem('ui', 'ssh',
546 coreconfigitem('ui', 'ssh',
547 default='ssh',
547 default='ssh',
548 )
548 )
549 coreconfigitem('ui', 'statuscopies',
549 coreconfigitem('ui', 'statuscopies',
550 default=False,
550 default=False,
551 )
551 )
552 coreconfigitem('ui', 'strict',
552 coreconfigitem('ui', 'strict',
553 default=False,
553 default=False,
554 )
554 )
555 coreconfigitem('ui', 'style',
555 coreconfigitem('ui', 'style',
556 default='',
556 default='',
557 )
557 )
558 coreconfigitem('ui', 'supportcontact',
558 coreconfigitem('ui', 'supportcontact',
559 default=None,
559 default=None,
560 )
560 )
561 coreconfigitem('ui', 'textwidth',
561 coreconfigitem('ui', 'textwidth',
562 default=78,
562 default=78,
563 )
563 )
564 coreconfigitem('ui', 'timeout',
564 coreconfigitem('ui', 'timeout',
565 default='600',
565 default='600',
566 )
566 )
567 coreconfigitem('ui', 'traceback',
567 coreconfigitem('ui', 'traceback',
568 default=False,
568 default=False,
569 )
569 )
570 coreconfigitem('ui', 'tweakdefaults',
570 coreconfigitem('ui', 'tweakdefaults',
571 default=False,
571 default=False,
572 )
572 )
573 coreconfigitem('ui', 'usehttp2',
573 coreconfigitem('ui', 'usehttp2',
574 default=False,
574 default=False,
575 )
575 )
576 coreconfigitem('ui', 'username',
576 coreconfigitem('ui', 'username',
577 alias=[('ui', 'user')]
577 alias=[('ui', 'user')]
578 )
578 )
579 coreconfigitem('ui', 'verbose',
579 coreconfigitem('ui', 'verbose',
580 default=False,
580 default=False,
581 )
581 )
582 coreconfigitem('verify', 'skipflags',
582 coreconfigitem('verify', 'skipflags',
583 default=None,
583 default=None,
584 )
584 )
585 coreconfigitem('worker', 'backgroundclose',
585 coreconfigitem('worker', 'backgroundclose',
586 default=dynamicdefault,
586 default=dynamicdefault,
587 )
587 )
588 # Windows defaults to a limit of 512 open files. A buffer of 128
588 # Windows defaults to a limit of 512 open files. A buffer of 128
589 # should give us enough headway.
589 # should give us enough headway.
590 coreconfigitem('worker', 'backgroundclosemaxqueue',
590 coreconfigitem('worker', 'backgroundclosemaxqueue',
591 default=384,
591 default=384,
592 )
592 )
593 coreconfigitem('worker', 'backgroundcloseminfilecount',
593 coreconfigitem('worker', 'backgroundcloseminfilecount',
594 default=2048,
594 default=2048,
595 )
595 )
596 coreconfigitem('worker', 'backgroundclosethreadcount',
596 coreconfigitem('worker', 'backgroundclosethreadcount',
597 default=4,
597 default=4,
598 )
598 )
599 coreconfigitem('worker', 'numcpus',
599 coreconfigitem('worker', 'numcpus',
600 default=None,
600 default=None,
601 )
601 )
@@ -1,743 +1,743 b''
1 # copies.py - copy detection for Mercurial
1 # copies.py - copy detection for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
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
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import heapq
10 import heapq
11
11
12 from . import (
12 from . import (
13 match as matchmod,
13 match as matchmod,
14 node,
14 node,
15 pathutil,
15 pathutil,
16 scmutil,
16 scmutil,
17 util,
17 util,
18 )
18 )
19
19
20 def _findlimit(repo, a, b):
20 def _findlimit(repo, a, b):
21 """
21 """
22 Find the last revision that needs to be checked to ensure that a full
22 Find the last revision that needs to be checked to ensure that a full
23 transitive closure for file copies can be properly calculated.
23 transitive closure for file copies can be properly calculated.
24 Generally, this means finding the earliest revision number that's an
24 Generally, this means finding the earliest revision number that's an
25 ancestor of a or b but not both, except when a or b is a direct descendent
25 ancestor of a or b but not both, except when a or b is a direct descendent
26 of the other, in which case we can return the minimum revnum of a and b.
26 of the other, in which case we can return the minimum revnum of a and b.
27 None if no such revision exists.
27 None if no such revision exists.
28 """
28 """
29
29
30 # basic idea:
30 # basic idea:
31 # - mark a and b with different sides
31 # - mark a and b with different sides
32 # - if a parent's children are all on the same side, the parent is
32 # - if a parent's children are all on the same side, the parent is
33 # on that side, otherwise it is on no side
33 # on that side, otherwise it is on no side
34 # - walk the graph in topological order with the help of a heap;
34 # - walk the graph in topological order with the help of a heap;
35 # - add unseen parents to side map
35 # - add unseen parents to side map
36 # - clear side of any parent that has children on different sides
36 # - clear side of any parent that has children on different sides
37 # - track number of interesting revs that might still be on a side
37 # - track number of interesting revs that might still be on a side
38 # - track the lowest interesting rev seen
38 # - track the lowest interesting rev seen
39 # - quit when interesting revs is zero
39 # - quit when interesting revs is zero
40
40
41 cl = repo.changelog
41 cl = repo.changelog
42 working = len(cl) # pseudo rev for the working directory
42 working = len(cl) # pseudo rev for the working directory
43 if a is None:
43 if a is None:
44 a = working
44 a = working
45 if b is None:
45 if b is None:
46 b = working
46 b = working
47
47
48 side = {a: -1, b: 1}
48 side = {a: -1, b: 1}
49 visit = [-a, -b]
49 visit = [-a, -b]
50 heapq.heapify(visit)
50 heapq.heapify(visit)
51 interesting = len(visit)
51 interesting = len(visit)
52 hascommonancestor = False
52 hascommonancestor = False
53 limit = working
53 limit = working
54
54
55 while interesting:
55 while interesting:
56 r = -heapq.heappop(visit)
56 r = -heapq.heappop(visit)
57 if r == working:
57 if r == working:
58 parents = [cl.rev(p) for p in repo.dirstate.parents()]
58 parents = [cl.rev(p) for p in repo.dirstate.parents()]
59 else:
59 else:
60 parents = cl.parentrevs(r)
60 parents = cl.parentrevs(r)
61 for p in parents:
61 for p in parents:
62 if p < 0:
62 if p < 0:
63 continue
63 continue
64 if p not in side:
64 if p not in side:
65 # first time we see p; add it to visit
65 # first time we see p; add it to visit
66 side[p] = side[r]
66 side[p] = side[r]
67 if side[p]:
67 if side[p]:
68 interesting += 1
68 interesting += 1
69 heapq.heappush(visit, -p)
69 heapq.heappush(visit, -p)
70 elif side[p] and side[p] != side[r]:
70 elif side[p] and side[p] != side[r]:
71 # p was interesting but now we know better
71 # p was interesting but now we know better
72 side[p] = 0
72 side[p] = 0
73 interesting -= 1
73 interesting -= 1
74 hascommonancestor = True
74 hascommonancestor = True
75 if side[r]:
75 if side[r]:
76 limit = r # lowest rev visited
76 limit = r # lowest rev visited
77 interesting -= 1
77 interesting -= 1
78
78
79 if not hascommonancestor:
79 if not hascommonancestor:
80 return None
80 return None
81
81
82 # Consider the following flow (see test-commit-amend.t under issue4405):
82 # Consider the following flow (see test-commit-amend.t under issue4405):
83 # 1/ File 'a0' committed
83 # 1/ File 'a0' committed
84 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
84 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
85 # 3/ Move back to first commit
85 # 3/ Move back to first commit
86 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
86 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
87 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
87 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
88 #
88 #
89 # During the amend in step five, we will be in this state:
89 # During the amend in step five, we will be in this state:
90 #
90 #
91 # @ 3 temporary amend commit for a1-amend
91 # @ 3 temporary amend commit for a1-amend
92 # |
92 # |
93 # o 2 a1-amend
93 # o 2 a1-amend
94 # |
94 # |
95 # | o 1 a1
95 # | o 1 a1
96 # |/
96 # |/
97 # o 0 a0
97 # o 0 a0
98 #
98 #
99 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
99 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
100 # yet the filelog has the copy information in rev 1 and we will not look
100 # yet the filelog has the copy information in rev 1 and we will not look
101 # back far enough unless we also look at the a and b as candidates.
101 # back far enough unless we also look at the a and b as candidates.
102 # This only occurs when a is a descendent of b or visa-versa.
102 # This only occurs when a is a descendent of b or visa-versa.
103 return min(limit, a, b)
103 return min(limit, a, b)
104
104
105 def _chain(src, dst, a, b):
105 def _chain(src, dst, a, b):
106 '''chain two sets of copies a->b'''
106 '''chain two sets of copies a->b'''
107 t = a.copy()
107 t = a.copy()
108 for k, v in b.iteritems():
108 for k, v in b.iteritems():
109 if v in t:
109 if v in t:
110 # found a chain
110 # found a chain
111 if t[v] != k:
111 if t[v] != k:
112 # file wasn't renamed back to itself
112 # file wasn't renamed back to itself
113 t[k] = t[v]
113 t[k] = t[v]
114 if v not in dst:
114 if v not in dst:
115 # chain was a rename, not a copy
115 # chain was a rename, not a copy
116 del t[v]
116 del t[v]
117 if v in src:
117 if v in src:
118 # file is a copy of an existing file
118 # file is a copy of an existing file
119 t[k] = v
119 t[k] = v
120
120
121 # remove criss-crossed copies
121 # remove criss-crossed copies
122 for k, v in t.items():
122 for k, v in t.items():
123 if k in src and v in dst:
123 if k in src and v in dst:
124 del t[k]
124 del t[k]
125
125
126 return t
126 return t
127
127
128 def _tracefile(fctx, am, limit=-1):
128 def _tracefile(fctx, am, limit=-1):
129 '''return file context that is the ancestor of fctx present in ancestor
129 '''return file context that is the ancestor of fctx present in ancestor
130 manifest am, stopping after the first ancestor lower than limit'''
130 manifest am, stopping after the first ancestor lower than limit'''
131
131
132 for f in fctx.ancestors():
132 for f in fctx.ancestors():
133 if am.get(f.path(), None) == f.filenode():
133 if am.get(f.path(), None) == f.filenode():
134 return f
134 return f
135 if limit >= 0 and f.linkrev() < limit and f.rev() < limit:
135 if limit >= 0 and f.linkrev() < limit and f.rev() < limit:
136 return None
136 return None
137
137
138 def _dirstatecopies(d):
138 def _dirstatecopies(d):
139 ds = d._repo.dirstate
139 ds = d._repo.dirstate
140 c = ds.copies().copy()
140 c = ds.copies().copy()
141 for k in c.keys():
141 for k in c.keys():
142 if ds[k] not in 'anm':
142 if ds[k] not in 'anm':
143 del c[k]
143 del c[k]
144 return c
144 return c
145
145
146 def _computeforwardmissing(a, b, match=None):
146 def _computeforwardmissing(a, b, match=None):
147 """Computes which files are in b but not a.
147 """Computes which files are in b but not a.
148 This is its own function so extensions can easily wrap this call to see what
148 This is its own function so extensions can easily wrap this call to see what
149 files _forwardcopies is about to process.
149 files _forwardcopies is about to process.
150 """
150 """
151 ma = a.manifest()
151 ma = a.manifest()
152 mb = b.manifest()
152 mb = b.manifest()
153 return mb.filesnotin(ma, match=match)
153 return mb.filesnotin(ma, match=match)
154
154
155 def _forwardcopies(a, b, match=None):
155 def _forwardcopies(a, b, match=None):
156 '''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
156 '''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
157
157
158 # check for working copy
158 # check for working copy
159 w = None
159 w = None
160 if b.rev() is None:
160 if b.rev() is None:
161 w = b
161 w = b
162 b = w.p1()
162 b = w.p1()
163 if a == b:
163 if a == b:
164 # short-circuit to avoid issues with merge states
164 # short-circuit to avoid issues with merge states
165 return _dirstatecopies(w)
165 return _dirstatecopies(w)
166
166
167 # files might have to be traced back to the fctx parent of the last
167 # files might have to be traced back to the fctx parent of the last
168 # one-side-only changeset, but not further back than that
168 # one-side-only changeset, but not further back than that
169 limit = _findlimit(a._repo, a.rev(), b.rev())
169 limit = _findlimit(a._repo, a.rev(), b.rev())
170 if limit is None:
170 if limit is None:
171 limit = -1
171 limit = -1
172 am = a.manifest()
172 am = a.manifest()
173
173
174 # find where new files came from
174 # find where new files came from
175 # we currently don't try to find where old files went, too expensive
175 # we currently don't try to find where old files went, too expensive
176 # this means we can miss a case like 'hg rm b; hg cp a b'
176 # this means we can miss a case like 'hg rm b; hg cp a b'
177 cm = {}
177 cm = {}
178
178
179 # Computing the forward missing is quite expensive on large manifests, since
179 # Computing the forward missing is quite expensive on large manifests, since
180 # it compares the entire manifests. We can optimize it in the common use
180 # it compares the entire manifests. We can optimize it in the common use
181 # case of computing what copies are in a commit versus its parent (like
181 # case of computing what copies are in a commit versus its parent (like
182 # during a rebase or histedit). Note, we exclude merge commits from this
182 # during a rebase or histedit). Note, we exclude merge commits from this
183 # optimization, since the ctx.files() for a merge commit is not correct for
183 # optimization, since the ctx.files() for a merge commit is not correct for
184 # this comparison.
184 # this comparison.
185 forwardmissingmatch = match
185 forwardmissingmatch = match
186 if b.p1() == a and b.p2().node() == node.nullid:
186 if b.p1() == a and b.p2().node() == node.nullid:
187 filesmatcher = scmutil.matchfiles(a._repo, b.files())
187 filesmatcher = scmutil.matchfiles(a._repo, b.files())
188 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
188 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
189 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
189 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
190
190
191 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
191 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
192 for f in missing:
192 for f in missing:
193 fctx = b[f]
193 fctx = b[f]
194 fctx._ancestrycontext = ancestrycontext
194 fctx._ancestrycontext = ancestrycontext
195 ofctx = _tracefile(fctx, am, limit)
195 ofctx = _tracefile(fctx, am, limit)
196 if ofctx:
196 if ofctx:
197 cm[f] = ofctx.path()
197 cm[f] = ofctx.path()
198
198
199 # combine copies from dirstate if necessary
199 # combine copies from dirstate if necessary
200 if w is not None:
200 if w is not None:
201 cm = _chain(a, w, cm, _dirstatecopies(w))
201 cm = _chain(a, w, cm, _dirstatecopies(w))
202
202
203 return cm
203 return cm
204
204
205 def _backwardrenames(a, b):
205 def _backwardrenames(a, b):
206 if a._repo.ui.configbool('experimental', 'disablecopytrace'):
206 if a._repo.ui.config('experimental', 'copytrace') == 'off':
207 return {}
207 return {}
208
208
209 # Even though we're not taking copies into account, 1:n rename situations
209 # Even though we're not taking copies into account, 1:n rename situations
210 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
210 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
211 # arbitrarily pick one of the renames.
211 # arbitrarily pick one of the renames.
212 f = _forwardcopies(b, a)
212 f = _forwardcopies(b, a)
213 r = {}
213 r = {}
214 for k, v in sorted(f.iteritems()):
214 for k, v in sorted(f.iteritems()):
215 # remove copies
215 # remove copies
216 if v in a:
216 if v in a:
217 continue
217 continue
218 r[v] = k
218 r[v] = k
219 return r
219 return r
220
220
221 def pathcopies(x, y, match=None):
221 def pathcopies(x, y, match=None):
222 '''find {dst@y: src@x} copy mapping for directed compare'''
222 '''find {dst@y: src@x} copy mapping for directed compare'''
223 if x == y or not x or not y:
223 if x == y or not x or not y:
224 return {}
224 return {}
225 a = y.ancestor(x)
225 a = y.ancestor(x)
226 if a == x:
226 if a == x:
227 return _forwardcopies(x, y, match=match)
227 return _forwardcopies(x, y, match=match)
228 if a == y:
228 if a == y:
229 return _backwardrenames(x, y)
229 return _backwardrenames(x, y)
230 return _chain(x, y, _backwardrenames(x, a),
230 return _chain(x, y, _backwardrenames(x, a),
231 _forwardcopies(a, y, match=match))
231 _forwardcopies(a, y, match=match))
232
232
233 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
233 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
234 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
234 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
235 and c2. This is its own function so extensions can easily wrap this call
235 and c2. This is its own function so extensions can easily wrap this call
236 to see what files mergecopies is about to process.
236 to see what files mergecopies is about to process.
237
237
238 Even though c1 and c2 are not used in this function, they are useful in
238 Even though c1 and c2 are not used in this function, they are useful in
239 other extensions for being able to read the file nodes of the changed files.
239 other extensions for being able to read the file nodes of the changed files.
240
240
241 "baselabel" can be passed to help distinguish the multiple computations
241 "baselabel" can be passed to help distinguish the multiple computations
242 done in the graft case.
242 done in the graft case.
243 """
243 """
244 u1 = sorted(addedinm1 - addedinm2)
244 u1 = sorted(addedinm1 - addedinm2)
245 u2 = sorted(addedinm2 - addedinm1)
245 u2 = sorted(addedinm2 - addedinm1)
246
246
247 header = " unmatched files in %s"
247 header = " unmatched files in %s"
248 if baselabel:
248 if baselabel:
249 header += ' (from %s)' % baselabel
249 header += ' (from %s)' % baselabel
250 if u1:
250 if u1:
251 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
251 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
252 if u2:
252 if u2:
253 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
253 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
254 return u1, u2
254 return u1, u2
255
255
256 def _makegetfctx(ctx):
256 def _makegetfctx(ctx):
257 """return a 'getfctx' function suitable for _checkcopies usage
257 """return a 'getfctx' function suitable for _checkcopies usage
258
258
259 We have to re-setup the function building 'filectx' for each
259 We have to re-setup the function building 'filectx' for each
260 '_checkcopies' to ensure the linkrev adjustment is properly setup for
260 '_checkcopies' to ensure the linkrev adjustment is properly setup for
261 each. Linkrev adjustment is important to avoid bug in rename
261 each. Linkrev adjustment is important to avoid bug in rename
262 detection. Moreover, having a proper '_ancestrycontext' setup ensures
262 detection. Moreover, having a proper '_ancestrycontext' setup ensures
263 the performance impact of this adjustment is kept limited. Without it,
263 the performance impact of this adjustment is kept limited. Without it,
264 each file could do a full dag traversal making the time complexity of
264 each file could do a full dag traversal making the time complexity of
265 the operation explode (see issue4537).
265 the operation explode (see issue4537).
266
266
267 This function exists here mostly to limit the impact on stable. Feel
267 This function exists here mostly to limit the impact on stable. Feel
268 free to refactor on default.
268 free to refactor on default.
269 """
269 """
270 rev = ctx.rev()
270 rev = ctx.rev()
271 repo = ctx._repo
271 repo = ctx._repo
272 ac = getattr(ctx, '_ancestrycontext', None)
272 ac = getattr(ctx, '_ancestrycontext', None)
273 if ac is None:
273 if ac is None:
274 revs = [rev]
274 revs = [rev]
275 if rev is None:
275 if rev is None:
276 revs = [p.rev() for p in ctx.parents()]
276 revs = [p.rev() for p in ctx.parents()]
277 ac = repo.changelog.ancestors(revs, inclusive=True)
277 ac = repo.changelog.ancestors(revs, inclusive=True)
278 ctx._ancestrycontext = ac
278 ctx._ancestrycontext = ac
279 def makectx(f, n):
279 def makectx(f, n):
280 if n in node.wdirnodes: # in a working context?
280 if n in node.wdirnodes: # in a working context?
281 if ctx.rev() is None:
281 if ctx.rev() is None:
282 return ctx.filectx(f)
282 return ctx.filectx(f)
283 return repo[None][f]
283 return repo[None][f]
284 fctx = repo.filectx(f, fileid=n)
284 fctx = repo.filectx(f, fileid=n)
285 # setup only needed for filectx not create from a changectx
285 # setup only needed for filectx not create from a changectx
286 fctx._ancestrycontext = ac
286 fctx._ancestrycontext = ac
287 fctx._descendantrev = rev
287 fctx._descendantrev = rev
288 return fctx
288 return fctx
289 return util.lrucachefunc(makectx)
289 return util.lrucachefunc(makectx)
290
290
291 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
291 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
292 """combine partial copy paths"""
292 """combine partial copy paths"""
293 remainder = {}
293 remainder = {}
294 for f in copyfrom:
294 for f in copyfrom:
295 if f in copyto:
295 if f in copyto:
296 finalcopy[copyto[f]] = copyfrom[f]
296 finalcopy[copyto[f]] = copyfrom[f]
297 del copyto[f]
297 del copyto[f]
298 for f in incompletediverge:
298 for f in incompletediverge:
299 assert f not in diverge
299 assert f not in diverge
300 ic = incompletediverge[f]
300 ic = incompletediverge[f]
301 if ic[0] in copyto:
301 if ic[0] in copyto:
302 diverge[f] = [copyto[ic[0]], ic[1]]
302 diverge[f] = [copyto[ic[0]], ic[1]]
303 else:
303 else:
304 remainder[f] = ic
304 remainder[f] = ic
305 return remainder
305 return remainder
306
306
307 def mergecopies(repo, c1, c2, base):
307 def mergecopies(repo, c1, c2, base):
308 """
308 """
309 The basic algorithm for copytracing. Copytracing is used in commands like
309 The basic algorithm for copytracing. Copytracing is used in commands like
310 rebase, merge, unshelve, etc to merge files that were moved/ copied in one
310 rebase, merge, unshelve, etc to merge files that were moved/ copied in one
311 merge parent and modified in another. For example:
311 merge parent and modified in another. For example:
312
312
313 o ---> 4 another commit
313 o ---> 4 another commit
314 |
314 |
315 | o ---> 3 commit that modifies a.txt
315 | o ---> 3 commit that modifies a.txt
316 | /
316 | /
317 o / ---> 2 commit that moves a.txt to b.txt
317 o / ---> 2 commit that moves a.txt to b.txt
318 |/
318 |/
319 o ---> 1 merge base
319 o ---> 1 merge base
320
320
321 If we try to rebase revision 3 on revision 4, since there is no a.txt in
321 If we try to rebase revision 3 on revision 4, since there is no a.txt in
322 revision 4, and if user have copytrace disabled, we prints the following
322 revision 4, and if user have copytrace disabled, we prints the following
323 message:
323 message:
324
324
325 ```other changed <file> which local deleted```
325 ```other changed <file> which local deleted```
326
326
327 If copytrace is enabled, this function finds all the new files that were
327 If copytrace is enabled, this function finds all the new files that were
328 added from merge base up to the top commit (here 4), and for each file it
328 added from merge base up to the top commit (here 4), and for each file it
329 checks if this file was copied from another file (a.txt in the above case).
329 checks if this file was copied from another file (a.txt in the above case).
330
330
331 Find moves and copies between context c1 and c2 that are relevant
331 Find moves and copies between context c1 and c2 that are relevant
332 for merging. 'base' will be used as the merge base.
332 for merging. 'base' will be used as the merge base.
333
333
334 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
334 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
335 "dirmove".
335 "dirmove".
336
336
337 "copy" is a mapping from destination name -> source name,
337 "copy" is a mapping from destination name -> source name,
338 where source is in c1 and destination is in c2 or vice-versa.
338 where source is in c1 and destination is in c2 or vice-versa.
339
339
340 "movewithdir" is a mapping from source name -> destination name,
340 "movewithdir" is a mapping from source name -> destination name,
341 where the file at source present in one context but not the other
341 where the file at source present in one context but not the other
342 needs to be moved to destination by the merge process, because the
342 needs to be moved to destination by the merge process, because the
343 other context moved the directory it is in.
343 other context moved the directory it is in.
344
344
345 "diverge" is a mapping of source name -> list of destination names
345 "diverge" is a mapping of source name -> list of destination names
346 for divergent renames.
346 for divergent renames.
347
347
348 "renamedelete" is a mapping of source name -> list of destination
348 "renamedelete" is a mapping of source name -> list of destination
349 names for files deleted in c1 that were renamed in c2 or vice-versa.
349 names for files deleted in c1 that were renamed in c2 or vice-versa.
350
350
351 "dirmove" is a mapping of detected source dir -> destination dir renames.
351 "dirmove" is a mapping of detected source dir -> destination dir renames.
352 This is needed for handling changes to new files previously grafted into
352 This is needed for handling changes to new files previously grafted into
353 renamed directories.
353 renamed directories.
354 """
354 """
355 # avoid silly behavior for update from empty dir
355 # avoid silly behavior for update from empty dir
356 if not c1 or not c2 or c1 == c2:
356 if not c1 or not c2 or c1 == c2:
357 return {}, {}, {}, {}, {}
357 return {}, {}, {}, {}, {}
358
358
359 # avoid silly behavior for parent -> working dir
359 # avoid silly behavior for parent -> working dir
360 if c2.node() is None and c1.node() == repo.dirstate.p1():
360 if c2.node() is None and c1.node() == repo.dirstate.p1():
361 return repo.dirstate.copies(), {}, {}, {}, {}
361 return repo.dirstate.copies(), {}, {}, {}, {}
362
362
363 # Copy trace disabling is explicitly below the node == p1 logic above
363 # Copy trace disabling is explicitly below the node == p1 logic above
364 # because the logic above is required for a simple copy to be kept across a
364 # because the logic above is required for a simple copy to be kept across a
365 # rebase.
365 # rebase.
366 if repo.ui.configbool('experimental', 'disablecopytrace'):
366 if repo.ui.config('experimental', 'copytrace') == 'off':
367 return {}, {}, {}, {}, {}
367 return {}, {}, {}, {}, {}
368
368
369 # In certain scenarios (e.g. graft, update or rebase), base can be
369 # In certain scenarios (e.g. graft, update or rebase), base can be
370 # overridden We still need to know a real common ancestor in this case We
370 # overridden We still need to know a real common ancestor in this case We
371 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
371 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
372 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
372 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
373 # caller may not know if the revision passed in lieu of the CA is a genuine
373 # caller may not know if the revision passed in lieu of the CA is a genuine
374 # common ancestor or not without explicitly checking it, it's better to
374 # common ancestor or not without explicitly checking it, it's better to
375 # determine that here.
375 # determine that here.
376 #
376 #
377 # base.descendant(wc) and base.descendant(base) are False, work around that
377 # base.descendant(wc) and base.descendant(base) are False, work around that
378 _c1 = c1.p1() if c1.rev() is None else c1
378 _c1 = c1.p1() if c1.rev() is None else c1
379 _c2 = c2.p1() if c2.rev() is None else c2
379 _c2 = c2.p1() if c2.rev() is None else c2
380 # an endpoint is "dirty" if it isn't a descendant of the merge base
380 # an endpoint is "dirty" if it isn't a descendant of the merge base
381 # if we have a dirty endpoint, we need to trigger graft logic, and also
381 # if we have a dirty endpoint, we need to trigger graft logic, and also
382 # keep track of which endpoint is dirty
382 # keep track of which endpoint is dirty
383 dirtyc1 = not (base == _c1 or base.descendant(_c1))
383 dirtyc1 = not (base == _c1 or base.descendant(_c1))
384 dirtyc2 = not (base == _c2 or base.descendant(_c2))
384 dirtyc2 = not (base == _c2 or base.descendant(_c2))
385 graft = dirtyc1 or dirtyc2
385 graft = dirtyc1 or dirtyc2
386 tca = base
386 tca = base
387 if graft:
387 if graft:
388 tca = _c1.ancestor(_c2)
388 tca = _c1.ancestor(_c2)
389
389
390 limit = _findlimit(repo, c1.rev(), c2.rev())
390 limit = _findlimit(repo, c1.rev(), c2.rev())
391 if limit is None:
391 if limit is None:
392 # no common ancestor, no copies
392 # no common ancestor, no copies
393 return {}, {}, {}, {}, {}
393 return {}, {}, {}, {}, {}
394 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
394 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
395
395
396 m1 = c1.manifest()
396 m1 = c1.manifest()
397 m2 = c2.manifest()
397 m2 = c2.manifest()
398 mb = base.manifest()
398 mb = base.manifest()
399
399
400 # gather data from _checkcopies:
400 # gather data from _checkcopies:
401 # - diverge = record all diverges in this dict
401 # - diverge = record all diverges in this dict
402 # - copy = record all non-divergent copies in this dict
402 # - copy = record all non-divergent copies in this dict
403 # - fullcopy = record all copies in this dict
403 # - fullcopy = record all copies in this dict
404 # - incomplete = record non-divergent partial copies here
404 # - incomplete = record non-divergent partial copies here
405 # - incompletediverge = record divergent partial copies here
405 # - incompletediverge = record divergent partial copies here
406 diverge = {} # divergence data is shared
406 diverge = {} # divergence data is shared
407 incompletediverge = {}
407 incompletediverge = {}
408 data1 = {'copy': {},
408 data1 = {'copy': {},
409 'fullcopy': {},
409 'fullcopy': {},
410 'incomplete': {},
410 'incomplete': {},
411 'diverge': diverge,
411 'diverge': diverge,
412 'incompletediverge': incompletediverge,
412 'incompletediverge': incompletediverge,
413 }
413 }
414 data2 = {'copy': {},
414 data2 = {'copy': {},
415 'fullcopy': {},
415 'fullcopy': {},
416 'incomplete': {},
416 'incomplete': {},
417 'diverge': diverge,
417 'diverge': diverge,
418 'incompletediverge': incompletediverge,
418 'incompletediverge': incompletediverge,
419 }
419 }
420
420
421 # find interesting file sets from manifests
421 # find interesting file sets from manifests
422 addedinm1 = m1.filesnotin(mb)
422 addedinm1 = m1.filesnotin(mb)
423 addedinm2 = m2.filesnotin(mb)
423 addedinm2 = m2.filesnotin(mb)
424 bothnew = sorted(addedinm1 & addedinm2)
424 bothnew = sorted(addedinm1 & addedinm2)
425 if tca == base:
425 if tca == base:
426 # unmatched file from base
426 # unmatched file from base
427 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
427 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
428 u1u, u2u = u1r, u2r
428 u1u, u2u = u1r, u2r
429 else:
429 else:
430 # unmatched file from base (DAG rotation in the graft case)
430 # unmatched file from base (DAG rotation in the graft case)
431 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
431 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
432 baselabel='base')
432 baselabel='base')
433 # unmatched file from topological common ancestors (no DAG rotation)
433 # unmatched file from topological common ancestors (no DAG rotation)
434 # need to recompute this for directory move handling when grafting
434 # need to recompute this for directory move handling when grafting
435 mta = tca.manifest()
435 mta = tca.manifest()
436 u1u, u2u = _computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
436 u1u, u2u = _computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
437 m2.filesnotin(mta),
437 m2.filesnotin(mta),
438 baselabel='topological common ancestor')
438 baselabel='topological common ancestor')
439
439
440 for f in u1u:
440 for f in u1u:
441 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
441 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
442
442
443 for f in u2u:
443 for f in u2u:
444 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
444 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
445
445
446 copy = dict(data1['copy'])
446 copy = dict(data1['copy'])
447 copy.update(data2['copy'])
447 copy.update(data2['copy'])
448 fullcopy = dict(data1['fullcopy'])
448 fullcopy = dict(data1['fullcopy'])
449 fullcopy.update(data2['fullcopy'])
449 fullcopy.update(data2['fullcopy'])
450
450
451 if dirtyc1:
451 if dirtyc1:
452 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
452 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
453 incompletediverge)
453 incompletediverge)
454 else:
454 else:
455 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
455 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
456 incompletediverge)
456 incompletediverge)
457
457
458 renamedelete = {}
458 renamedelete = {}
459 renamedeleteset = set()
459 renamedeleteset = set()
460 divergeset = set()
460 divergeset = set()
461 for of, fl in diverge.items():
461 for of, fl in diverge.items():
462 if len(fl) == 1 or of in c1 or of in c2:
462 if len(fl) == 1 or of in c1 or of in c2:
463 del diverge[of] # not actually divergent, or not a rename
463 del diverge[of] # not actually divergent, or not a rename
464 if of not in c1 and of not in c2:
464 if of not in c1 and of not in c2:
465 # renamed on one side, deleted on the other side, but filter
465 # renamed on one side, deleted on the other side, but filter
466 # out files that have been renamed and then deleted
466 # out files that have been renamed and then deleted
467 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
467 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
468 renamedeleteset.update(fl) # reverse map for below
468 renamedeleteset.update(fl) # reverse map for below
469 else:
469 else:
470 divergeset.update(fl) # reverse map for below
470 divergeset.update(fl) # reverse map for below
471
471
472 if bothnew:
472 if bothnew:
473 repo.ui.debug(" unmatched files new in both:\n %s\n"
473 repo.ui.debug(" unmatched files new in both:\n %s\n"
474 % "\n ".join(bothnew))
474 % "\n ".join(bothnew))
475 bothdiverge = {}
475 bothdiverge = {}
476 bothincompletediverge = {}
476 bothincompletediverge = {}
477 remainder = {}
477 remainder = {}
478 both1 = {'copy': {},
478 both1 = {'copy': {},
479 'fullcopy': {},
479 'fullcopy': {},
480 'incomplete': {},
480 'incomplete': {},
481 'diverge': bothdiverge,
481 'diverge': bothdiverge,
482 'incompletediverge': bothincompletediverge
482 'incompletediverge': bothincompletediverge
483 }
483 }
484 both2 = {'copy': {},
484 both2 = {'copy': {},
485 'fullcopy': {},
485 'fullcopy': {},
486 'incomplete': {},
486 'incomplete': {},
487 'diverge': bothdiverge,
487 'diverge': bothdiverge,
488 'incompletediverge': bothincompletediverge
488 'incompletediverge': bothincompletediverge
489 }
489 }
490 for f in bothnew:
490 for f in bothnew:
491 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
491 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
492 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
492 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
493 if dirtyc1:
493 if dirtyc1:
494 # incomplete copies may only be found on the "dirty" side for bothnew
494 # incomplete copies may only be found on the "dirty" side for bothnew
495 assert not both2['incomplete']
495 assert not both2['incomplete']
496 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
496 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
497 bothincompletediverge)
497 bothincompletediverge)
498 elif dirtyc2:
498 elif dirtyc2:
499 assert not both1['incomplete']
499 assert not both1['incomplete']
500 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
500 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
501 bothincompletediverge)
501 bothincompletediverge)
502 else:
502 else:
503 # incomplete copies and divergences can't happen outside grafts
503 # incomplete copies and divergences can't happen outside grafts
504 assert not both1['incomplete']
504 assert not both1['incomplete']
505 assert not both2['incomplete']
505 assert not both2['incomplete']
506 assert not bothincompletediverge
506 assert not bothincompletediverge
507 for f in remainder:
507 for f in remainder:
508 assert f not in bothdiverge
508 assert f not in bothdiverge
509 ic = remainder[f]
509 ic = remainder[f]
510 if ic[0] in (m1 if dirtyc1 else m2):
510 if ic[0] in (m1 if dirtyc1 else m2):
511 # backed-out rename on one side, but watch out for deleted files
511 # backed-out rename on one side, but watch out for deleted files
512 bothdiverge[f] = ic
512 bothdiverge[f] = ic
513 for of, fl in bothdiverge.items():
513 for of, fl in bothdiverge.items():
514 if len(fl) == 2 and fl[0] == fl[1]:
514 if len(fl) == 2 and fl[0] == fl[1]:
515 copy[fl[0]] = of # not actually divergent, just matching renames
515 copy[fl[0]] = of # not actually divergent, just matching renames
516
516
517 if fullcopy and repo.ui.debugflag:
517 if fullcopy and repo.ui.debugflag:
518 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
518 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
519 "% = renamed and deleted):\n")
519 "% = renamed and deleted):\n")
520 for f in sorted(fullcopy):
520 for f in sorted(fullcopy):
521 note = ""
521 note = ""
522 if f in copy:
522 if f in copy:
523 note += "*"
523 note += "*"
524 if f in divergeset:
524 if f in divergeset:
525 note += "!"
525 note += "!"
526 if f in renamedeleteset:
526 if f in renamedeleteset:
527 note += "%"
527 note += "%"
528 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
528 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
529 note))
529 note))
530 del divergeset
530 del divergeset
531
531
532 if not fullcopy:
532 if not fullcopy:
533 return copy, {}, diverge, renamedelete, {}
533 return copy, {}, diverge, renamedelete, {}
534
534
535 repo.ui.debug(" checking for directory renames\n")
535 repo.ui.debug(" checking for directory renames\n")
536
536
537 # generate a directory move map
537 # generate a directory move map
538 d1, d2 = c1.dirs(), c2.dirs()
538 d1, d2 = c1.dirs(), c2.dirs()
539 # Hack for adding '', which is not otherwise added, to d1 and d2
539 # Hack for adding '', which is not otherwise added, to d1 and d2
540 d1.addpath('/')
540 d1.addpath('/')
541 d2.addpath('/')
541 d2.addpath('/')
542 invalid = set()
542 invalid = set()
543 dirmove = {}
543 dirmove = {}
544
544
545 # examine each file copy for a potential directory move, which is
545 # examine each file copy for a potential directory move, which is
546 # when all the files in a directory are moved to a new directory
546 # when all the files in a directory are moved to a new directory
547 for dst, src in fullcopy.iteritems():
547 for dst, src in fullcopy.iteritems():
548 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
548 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
549 if dsrc in invalid:
549 if dsrc in invalid:
550 # already seen to be uninteresting
550 # already seen to be uninteresting
551 continue
551 continue
552 elif dsrc in d1 and ddst in d1:
552 elif dsrc in d1 and ddst in d1:
553 # directory wasn't entirely moved locally
553 # directory wasn't entirely moved locally
554 invalid.add(dsrc + "/")
554 invalid.add(dsrc + "/")
555 elif dsrc in d2 and ddst in d2:
555 elif dsrc in d2 and ddst in d2:
556 # directory wasn't entirely moved remotely
556 # directory wasn't entirely moved remotely
557 invalid.add(dsrc + "/")
557 invalid.add(dsrc + "/")
558 elif dsrc + "/" in dirmove and dirmove[dsrc + "/"] != ddst + "/":
558 elif dsrc + "/" in dirmove and dirmove[dsrc + "/"] != ddst + "/":
559 # files from the same directory moved to two different places
559 # files from the same directory moved to two different places
560 invalid.add(dsrc + "/")
560 invalid.add(dsrc + "/")
561 else:
561 else:
562 # looks good so far
562 # looks good so far
563 dirmove[dsrc + "/"] = ddst + "/"
563 dirmove[dsrc + "/"] = ddst + "/"
564
564
565 for i in invalid:
565 for i in invalid:
566 if i in dirmove:
566 if i in dirmove:
567 del dirmove[i]
567 del dirmove[i]
568 del d1, d2, invalid
568 del d1, d2, invalid
569
569
570 if not dirmove:
570 if not dirmove:
571 return copy, {}, diverge, renamedelete, {}
571 return copy, {}, diverge, renamedelete, {}
572
572
573 for d in dirmove:
573 for d in dirmove:
574 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
574 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
575 (d, dirmove[d]))
575 (d, dirmove[d]))
576
576
577 movewithdir = {}
577 movewithdir = {}
578 # check unaccounted nonoverlapping files against directory moves
578 # check unaccounted nonoverlapping files against directory moves
579 for f in u1r + u2r:
579 for f in u1r + u2r:
580 if f not in fullcopy:
580 if f not in fullcopy:
581 for d in dirmove:
581 for d in dirmove:
582 if f.startswith(d):
582 if f.startswith(d):
583 # new file added in a directory that was moved, move it
583 # new file added in a directory that was moved, move it
584 df = dirmove[d] + f[len(d):]
584 df = dirmove[d] + f[len(d):]
585 if df not in copy:
585 if df not in copy:
586 movewithdir[f] = df
586 movewithdir[f] = df
587 repo.ui.debug((" pending file src: '%s' -> "
587 repo.ui.debug((" pending file src: '%s' -> "
588 "dst: '%s'\n") % (f, df))
588 "dst: '%s'\n") % (f, df))
589 break
589 break
590
590
591 return copy, movewithdir, diverge, renamedelete, dirmove
591 return copy, movewithdir, diverge, renamedelete, dirmove
592
592
593 def _related(f1, f2, limit):
593 def _related(f1, f2, limit):
594 """return True if f1 and f2 filectx have a common ancestor
594 """return True if f1 and f2 filectx have a common ancestor
595
595
596 Walk back to common ancestor to see if the two files originate
596 Walk back to common ancestor to see if the two files originate
597 from the same file. Since workingfilectx's rev() is None it messes
597 from the same file. Since workingfilectx's rev() is None it messes
598 up the integer comparison logic, hence the pre-step check for
598 up the integer comparison logic, hence the pre-step check for
599 None (f1 and f2 can only be workingfilectx's initially).
599 None (f1 and f2 can only be workingfilectx's initially).
600 """
600 """
601
601
602 if f1 == f2:
602 if f1 == f2:
603 return f1 # a match
603 return f1 # a match
604
604
605 g1, g2 = f1.ancestors(), f2.ancestors()
605 g1, g2 = f1.ancestors(), f2.ancestors()
606 try:
606 try:
607 f1r, f2r = f1.linkrev(), f2.linkrev()
607 f1r, f2r = f1.linkrev(), f2.linkrev()
608
608
609 if f1r is None:
609 if f1r is None:
610 f1 = next(g1)
610 f1 = next(g1)
611 if f2r is None:
611 if f2r is None:
612 f2 = next(g2)
612 f2 = next(g2)
613
613
614 while True:
614 while True:
615 f1r, f2r = f1.linkrev(), f2.linkrev()
615 f1r, f2r = f1.linkrev(), f2.linkrev()
616 if f1r > f2r:
616 if f1r > f2r:
617 f1 = next(g1)
617 f1 = next(g1)
618 elif f2r > f1r:
618 elif f2r > f1r:
619 f2 = next(g2)
619 f2 = next(g2)
620 elif f1 == f2:
620 elif f1 == f2:
621 return f1 # a match
621 return f1 # a match
622 elif f1r == f2r or f1r < limit or f2r < limit:
622 elif f1r == f2r or f1r < limit or f2r < limit:
623 return False # copy no longer relevant
623 return False # copy no longer relevant
624 except StopIteration:
624 except StopIteration:
625 return False
625 return False
626
626
627 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
627 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
628 """
628 """
629 check possible copies of f from msrc to mdst
629 check possible copies of f from msrc to mdst
630
630
631 srcctx = starting context for f in msrc
631 srcctx = starting context for f in msrc
632 dstctx = destination context for f in mdst
632 dstctx = destination context for f in mdst
633 f = the filename to check (as in msrc)
633 f = the filename to check (as in msrc)
634 base = the changectx used as a merge base
634 base = the changectx used as a merge base
635 tca = topological common ancestor for graft-like scenarios
635 tca = topological common ancestor for graft-like scenarios
636 remotebase = True if base is outside tca::srcctx, False otherwise
636 remotebase = True if base is outside tca::srcctx, False otherwise
637 limit = the rev number to not search beyond
637 limit = the rev number to not search beyond
638 data = dictionary of dictionary to store copy data. (see mergecopies)
638 data = dictionary of dictionary to store copy data. (see mergecopies)
639
639
640 note: limit is only an optimization, and provides no guarantee that
640 note: limit is only an optimization, and provides no guarantee that
641 irrelevant revisions will not be visited
641 irrelevant revisions will not be visited
642 there is no easy way to make this algorithm stop in a guaranteed way
642 there is no easy way to make this algorithm stop in a guaranteed way
643 once it "goes behind a certain revision".
643 once it "goes behind a certain revision".
644 """
644 """
645
645
646 msrc = srcctx.manifest()
646 msrc = srcctx.manifest()
647 mdst = dstctx.manifest()
647 mdst = dstctx.manifest()
648 mb = base.manifest()
648 mb = base.manifest()
649 mta = tca.manifest()
649 mta = tca.manifest()
650 # Might be true if this call is about finding backward renames,
650 # Might be true if this call is about finding backward renames,
651 # This happens in the case of grafts because the DAG is then rotated.
651 # This happens in the case of grafts because the DAG is then rotated.
652 # If the file exists in both the base and the source, we are not looking
652 # If the file exists in both the base and the source, we are not looking
653 # for a rename on the source side, but on the part of the DAG that is
653 # for a rename on the source side, but on the part of the DAG that is
654 # traversed backwards.
654 # traversed backwards.
655 #
655 #
656 # In the case there is both backward and forward renames (before and after
656 # In the case there is both backward and forward renames (before and after
657 # the base) this is more complicated as we must detect a divergence.
657 # the base) this is more complicated as we must detect a divergence.
658 # We use 'backwards = False' in that case.
658 # We use 'backwards = False' in that case.
659 backwards = not remotebase and base != tca and f in mb
659 backwards = not remotebase and base != tca and f in mb
660 getsrcfctx = _makegetfctx(srcctx)
660 getsrcfctx = _makegetfctx(srcctx)
661 getdstfctx = _makegetfctx(dstctx)
661 getdstfctx = _makegetfctx(dstctx)
662
662
663 if msrc[f] == mb.get(f) and not remotebase:
663 if msrc[f] == mb.get(f) and not remotebase:
664 # Nothing to merge
664 # Nothing to merge
665 return
665 return
666
666
667 of = None
667 of = None
668 seen = {f}
668 seen = {f}
669 for oc in getsrcfctx(f, msrc[f]).ancestors():
669 for oc in getsrcfctx(f, msrc[f]).ancestors():
670 ocr = oc.linkrev()
670 ocr = oc.linkrev()
671 of = oc.path()
671 of = oc.path()
672 if of in seen:
672 if of in seen:
673 # check limit late - grab last rename before
673 # check limit late - grab last rename before
674 if ocr < limit:
674 if ocr < limit:
675 break
675 break
676 continue
676 continue
677 seen.add(of)
677 seen.add(of)
678
678
679 # remember for dir rename detection
679 # remember for dir rename detection
680 if backwards:
680 if backwards:
681 data['fullcopy'][of] = f # grafting backwards through renames
681 data['fullcopy'][of] = f # grafting backwards through renames
682 else:
682 else:
683 data['fullcopy'][f] = of
683 data['fullcopy'][f] = of
684 if of not in mdst:
684 if of not in mdst:
685 continue # no match, keep looking
685 continue # no match, keep looking
686 if mdst[of] == mb.get(of):
686 if mdst[of] == mb.get(of):
687 return # no merge needed, quit early
687 return # no merge needed, quit early
688 c2 = getdstfctx(of, mdst[of])
688 c2 = getdstfctx(of, mdst[of])
689 # c2 might be a plain new file on added on destination side that is
689 # c2 might be a plain new file on added on destination side that is
690 # unrelated to the droids we are looking for.
690 # unrelated to the droids we are looking for.
691 cr = _related(oc, c2, tca.rev())
691 cr = _related(oc, c2, tca.rev())
692 if cr and (of == f or of == c2.path()): # non-divergent
692 if cr and (of == f or of == c2.path()): # non-divergent
693 if backwards:
693 if backwards:
694 data['copy'][of] = f
694 data['copy'][of] = f
695 elif of in mb:
695 elif of in mb:
696 data['copy'][f] = of
696 data['copy'][f] = of
697 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
697 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
698 data['copy'][of] = f
698 data['copy'][of] = f
699 del data['fullcopy'][f]
699 del data['fullcopy'][f]
700 data['fullcopy'][of] = f
700 data['fullcopy'][of] = f
701 else: # divergence w.r.t. graft CA on one side of topological CA
701 else: # divergence w.r.t. graft CA on one side of topological CA
702 for sf in seen:
702 for sf in seen:
703 if sf in mb:
703 if sf in mb:
704 assert sf not in data['diverge']
704 assert sf not in data['diverge']
705 data['diverge'][sf] = [f, of]
705 data['diverge'][sf] = [f, of]
706 break
706 break
707 return
707 return
708
708
709 if of in mta:
709 if of in mta:
710 if backwards or remotebase:
710 if backwards or remotebase:
711 data['incomplete'][of] = f
711 data['incomplete'][of] = f
712 else:
712 else:
713 for sf in seen:
713 for sf in seen:
714 if sf in mb:
714 if sf in mb:
715 if tca == base:
715 if tca == base:
716 data['diverge'].setdefault(sf, []).append(f)
716 data['diverge'].setdefault(sf, []).append(f)
717 else:
717 else:
718 data['incompletediverge'][sf] = [of, f]
718 data['incompletediverge'][sf] = [of, f]
719 return
719 return
720
720
721 def duplicatecopies(repo, rev, fromrev, skiprev=None):
721 def duplicatecopies(repo, rev, fromrev, skiprev=None):
722 '''reproduce copies from fromrev to rev in the dirstate
722 '''reproduce copies from fromrev to rev in the dirstate
723
723
724 If skiprev is specified, it's a revision that should be used to
724 If skiprev is specified, it's a revision that should be used to
725 filter copy records. Any copies that occur between fromrev and
725 filter copy records. Any copies that occur between fromrev and
726 skiprev will not be duplicated, even if they appear in the set of
726 skiprev will not be duplicated, even if they appear in the set of
727 copies between fromrev and rev.
727 copies between fromrev and rev.
728 '''
728 '''
729 exclude = {}
729 exclude = {}
730 if (skiprev is not None and
730 if (skiprev is not None and
731 not repo.ui.configbool('experimental', 'disablecopytrace')):
731 repo.ui.config('experimental', 'copytrace') != 'off'):
732 # disablecopytrace skips this line, but not the entire function because
732 # copytrace='off' skips this line, but not the entire function because
733 # the line below is O(size of the repo) during a rebase, while the rest
733 # the line below is O(size of the repo) during a rebase, while the rest
734 # of the function is much faster (and is required for carrying copy
734 # of the function is much faster (and is required for carrying copy
735 # metadata across the rebase anyway).
735 # metadata across the rebase anyway).
736 exclude = pathcopies(repo[fromrev], repo[skiprev])
736 exclude = pathcopies(repo[fromrev], repo[skiprev])
737 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
737 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
738 # copies.pathcopies returns backward renames, so dst might not
738 # copies.pathcopies returns backward renames, so dst might not
739 # actually be in the dirstate
739 # actually be in the dirstate
740 if dst in exclude:
740 if dst in exclude:
741 continue
741 continue
742 if repo.dirstate[dst] in "nma":
742 if repo.dirstate[dst] in "nma":
743 repo.dirstate.copy(src, dst)
743 repo.dirstate.copy(src, dst)
@@ -1,168 +1,168 b''
1 $ hg init t
1 $ hg init t
2 $ cd t
2 $ cd t
3
3
4 $ echo 1 > a
4 $ echo 1 > a
5 $ hg ci -qAm "first"
5 $ hg ci -qAm "first"
6
6
7 $ hg cp a b
7 $ hg cp a b
8 $ hg mv a c
8 $ hg mv a c
9 $ echo 2 >> b
9 $ echo 2 >> b
10 $ echo 2 >> c
10 $ echo 2 >> c
11
11
12 $ hg ci -qAm "second"
12 $ hg ci -qAm "second"
13
13
14 $ hg co -C 0
14 $ hg co -C 0
15 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
15 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
16
16
17 $ echo 0 > a
17 $ echo 0 > a
18 $ echo 1 >> a
18 $ echo 1 >> a
19
19
20 $ hg ci -qAm "other"
20 $ hg ci -qAm "other"
21
21
22 $ hg merge --debug
22 $ hg merge --debug
23 searching for copies back to rev 1
23 searching for copies back to rev 1
24 unmatched files in other:
24 unmatched files in other:
25 b
25 b
26 c
26 c
27 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
27 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
28 src: 'a' -> dst: 'b' *
28 src: 'a' -> dst: 'b' *
29 src: 'a' -> dst: 'c' *
29 src: 'a' -> dst: 'c' *
30 checking for directory renames
30 checking for directory renames
31 resolving manifests
31 resolving manifests
32 branchmerge: True, force: False, partial: False
32 branchmerge: True, force: False, partial: False
33 ancestor: b8bf91eeebbc, local: add3f11052fa+, remote: 17c05bb7fcb6
33 ancestor: b8bf91eeebbc, local: add3f11052fa+, remote: 17c05bb7fcb6
34 preserving a for resolve of b
34 preserving a for resolve of b
35 preserving a for resolve of c
35 preserving a for resolve of c
36 removing a
36 removing a
37 starting 4 threads for background file closing (?)
37 starting 4 threads for background file closing (?)
38 b: remote moved from a -> m (premerge)
38 b: remote moved from a -> m (premerge)
39 picked tool ':merge' for b (binary False symlink False changedelete False)
39 picked tool ':merge' for b (binary False symlink False changedelete False)
40 merging a and b to b
40 merging a and b to b
41 my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc
41 my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc
42 premerge successful
42 premerge successful
43 c: remote moved from a -> m (premerge)
43 c: remote moved from a -> m (premerge)
44 picked tool ':merge' for c (binary False symlink False changedelete False)
44 picked tool ':merge' for c (binary False symlink False changedelete False)
45 merging a and c to c
45 merging a and c to c
46 my c@add3f11052fa+ other c@17c05bb7fcb6 ancestor a@b8bf91eeebbc
46 my c@add3f11052fa+ other c@17c05bb7fcb6 ancestor a@b8bf91eeebbc
47 premerge successful
47 premerge successful
48 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
48 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
49 (branch merge, don't forget to commit)
49 (branch merge, don't forget to commit)
50
50
51 file b
51 file b
52 $ cat b
52 $ cat b
53 0
53 0
54 1
54 1
55 2
55 2
56
56
57 file c
57 file c
58 $ cat c
58 $ cat c
59 0
59 0
60 1
60 1
61 2
61 2
62
62
63 Test disabling copy tracing
63 Test disabling copy tracing
64
64
65 - first verify copy metadata was kept
65 - first verify copy metadata was kept
66
66
67 $ hg up -qC 2
67 $ hg up -qC 2
68 $ hg rebase --keep -d 1 -b 2 --config extensions.rebase=
68 $ hg rebase --keep -d 1 -b 2 --config extensions.rebase=
69 rebasing 2:add3f11052fa "other" (tip)
69 rebasing 2:add3f11052fa "other" (tip)
70 merging b and a to b
70 merging b and a to b
71 merging c and a to c
71 merging c and a to c
72
72
73 $ cat b
73 $ cat b
74 0
74 0
75 1
75 1
76 2
76 2
77
77
78 - next verify copy metadata is lost when disabled
78 - next verify copy metadata is lost when disabled
79
79
80 $ hg strip -r . --config extensions.strip=
80 $ hg strip -r . --config extensions.strip=
81 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
81 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 saved backup bundle to $TESTTMP/t/.hg/strip-backup/550bd84c0cd3-fc575957-backup.hg (glob)
82 saved backup bundle to $TESTTMP/t/.hg/strip-backup/550bd84c0cd3-fc575957-backup.hg (glob)
83 $ hg up -qC 2
83 $ hg up -qC 2
84 $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True --config ui.interactive=True << EOF
84 $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.copytrace=off --config ui.interactive=True << EOF
85 > c
85 > c
86 > EOF
86 > EOF
87 rebasing 2:add3f11052fa "other" (tip)
87 rebasing 2:add3f11052fa "other" (tip)
88 other [source] changed a which local [dest] deleted
88 other [source] changed a which local [dest] deleted
89 use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
89 use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
90
90
91 $ cat b
91 $ cat b
92 1
92 1
93 2
93 2
94
94
95 $ cd ..
95 $ cd ..
96
96
97 Verify disabling copy tracing still keeps copies from rebase source
97 Verify disabling copy tracing still keeps copies from rebase source
98
98
99 $ hg init copydisable
99 $ hg init copydisable
100 $ cd copydisable
100 $ cd copydisable
101 $ touch a
101 $ touch a
102 $ hg ci -Aqm 'add a'
102 $ hg ci -Aqm 'add a'
103 $ touch b
103 $ touch b
104 $ hg ci -Aqm 'add b, c'
104 $ hg ci -Aqm 'add b, c'
105 $ hg cp b x
105 $ hg cp b x
106 $ echo x >> x
106 $ echo x >> x
107 $ hg ci -qm 'copy b->x'
107 $ hg ci -qm 'copy b->x'
108 $ hg up -q 1
108 $ hg up -q 1
109 $ touch z
109 $ touch z
110 $ hg ci -Aqm 'add z'
110 $ hg ci -Aqm 'add z'
111 $ hg log -G -T '{rev} {desc}\n'
111 $ hg log -G -T '{rev} {desc}\n'
112 @ 3 add z
112 @ 3 add z
113 |
113 |
114 | o 2 copy b->x
114 | o 2 copy b->x
115 |/
115 |/
116 o 1 add b, c
116 o 1 add b, c
117 |
117 |
118 o 0 add a
118 o 0 add a
119
119
120 $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True
120 $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.copytrace=off
121 rebasing 2:6adcf8c12e7d "copy b->x"
121 rebasing 2:6adcf8c12e7d "copy b->x"
122 saved backup bundle to $TESTTMP/copydisable/.hg/strip-backup/6adcf8c12e7d-ce4b3e75-rebase.hg (glob)
122 saved backup bundle to $TESTTMP/copydisable/.hg/strip-backup/6adcf8c12e7d-ce4b3e75-rebase.hg (glob)
123 $ hg up -q 3
123 $ hg up -q 3
124 $ hg log -f x -T '{rev} {desc}\n'
124 $ hg log -f x -T '{rev} {desc}\n'
125 3 copy b->x
125 3 copy b->x
126 1 add b, c
126 1 add b, c
127
127
128 $ cd ../
128 $ cd ../
129
129
130 Verify we duplicate existing copies, instead of detecting them
130 Verify we duplicate existing copies, instead of detecting them
131
131
132 $ hg init copydisable3
132 $ hg init copydisable3
133 $ cd copydisable3
133 $ cd copydisable3
134 $ touch a
134 $ touch a
135 $ hg ci -Aqm 'add a'
135 $ hg ci -Aqm 'add a'
136 $ hg cp a b
136 $ hg cp a b
137 $ hg ci -Aqm 'copy a->b'
137 $ hg ci -Aqm 'copy a->b'
138 $ hg mv b c
138 $ hg mv b c
139 $ hg ci -Aqm 'move b->c'
139 $ hg ci -Aqm 'move b->c'
140 $ hg up -q 0
140 $ hg up -q 0
141 $ hg cp a b
141 $ hg cp a b
142 $ echo b >> b
142 $ echo b >> b
143 $ hg ci -Aqm 'copy a->b (2)'
143 $ hg ci -Aqm 'copy a->b (2)'
144 $ hg log -G -T '{rev} {desc}\n'
144 $ hg log -G -T '{rev} {desc}\n'
145 @ 3 copy a->b (2)
145 @ 3 copy a->b (2)
146 |
146 |
147 | o 2 move b->c
147 | o 2 move b->c
148 | |
148 | |
149 | o 1 copy a->b
149 | o 1 copy a->b
150 |/
150 |/
151 o 0 add a
151 o 0 add a
152
152
153 $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.disablecopytrace=True
153 $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.copytrace=off
154 rebasing 3:47e1a9e6273b "copy a->b (2)" (tip)
154 rebasing 3:47e1a9e6273b "copy a->b (2)" (tip)
155 saved backup bundle to $TESTTMP/copydisable3/.hg/strip-backup/47e1a9e6273b-2d099c59-rebase.hg (glob)
155 saved backup bundle to $TESTTMP/copydisable3/.hg/strip-backup/47e1a9e6273b-2d099c59-rebase.hg (glob)
156
156
157 $ hg log -G -f b
157 $ hg log -G -f b
158 @ changeset: 3:76024fb4b05b
158 @ changeset: 3:76024fb4b05b
159 : tag: tip
159 : tag: tip
160 : user: test
160 : user: test
161 : date: Thu Jan 01 00:00:00 1970 +0000
161 : date: Thu Jan 01 00:00:00 1970 +0000
162 : summary: copy a->b (2)
162 : summary: copy a->b (2)
163 :
163 :
164 o changeset: 0:ac82d8b1f7c4
164 o changeset: 0:ac82d8b1f7c4
165 user: test
165 user: test
166 date: Thu Jan 01 00:00:00 1970 +0000
166 date: Thu Jan 01 00:00:00 1970 +0000
167 summary: add a
167 summary: add a
168
168
General Comments 0
You need to be logged in to leave comments. Login now