##// END OF EJS Templates
merge: don't pay for pathconflicts if there are none
Arseniy Alekseyev -
r50781:3aa8e569 default
parent child Browse files
Show More
@@ -1,2488 +1,2491 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2006, 2007 Olivia Mackall <olivia@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
8
9 import collections
9 import collections
10 import struct
10 import struct
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import nullrev
13 from .node import nullrev
14 from .thirdparty import attr
14 from .thirdparty import attr
15 from .utils import stringutil
15 from .utils import stringutil
16 from .dirstateutils import timestamp
16 from .dirstateutils import timestamp
17 from . import (
17 from . import (
18 copies,
18 copies,
19 encoding,
19 encoding,
20 error,
20 error,
21 filemerge,
21 filemerge,
22 match as matchmod,
22 match as matchmod,
23 mergestate as mergestatemod,
23 mergestate as mergestatemod,
24 obsutil,
24 obsutil,
25 pathutil,
25 pathutil,
26 policy,
26 policy,
27 pycompat,
27 pycompat,
28 scmutil,
28 scmutil,
29 subrepoutil,
29 subrepoutil,
30 util,
30 util,
31 worker,
31 worker,
32 )
32 )
33
33
34 _pack = struct.pack
34 _pack = struct.pack
35 _unpack = struct.unpack
35 _unpack = struct.unpack
36
36
37
37
38 def _getcheckunknownconfig(repo, section, name):
38 def _getcheckunknownconfig(repo, section, name):
39 config = repo.ui.config(section, name)
39 config = repo.ui.config(section, name)
40 valid = [b'abort', b'ignore', b'warn']
40 valid = [b'abort', b'ignore', b'warn']
41 if config not in valid:
41 if config not in valid:
42 validstr = b', '.join([b"'" + v + b"'" for v in valid])
42 validstr = b', '.join([b"'" + v + b"'" for v in valid])
43 msg = _(b"%s.%s not valid ('%s' is none of %s)")
43 msg = _(b"%s.%s not valid ('%s' is none of %s)")
44 msg %= (section, name, config, validstr)
44 msg %= (section, name, config, validstr)
45 raise error.ConfigError(msg)
45 raise error.ConfigError(msg)
46 return config
46 return config
47
47
48
48
49 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
49 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
50 if wctx.isinmemory():
50 if wctx.isinmemory():
51 # Nothing to do in IMM because nothing in the "working copy" can be an
51 # Nothing to do in IMM because nothing in the "working copy" can be an
52 # unknown file.
52 # unknown file.
53 #
53 #
54 # Note that we should bail out here, not in ``_checkunknownfiles()``,
54 # Note that we should bail out here, not in ``_checkunknownfiles()``,
55 # because that function does other useful work.
55 # because that function does other useful work.
56 return False
56 return False
57
57
58 if f2 is None:
58 if f2 is None:
59 f2 = f
59 f2 = f
60 return (
60 return (
61 repo.wvfs.audit.check(f)
61 repo.wvfs.audit.check(f)
62 and repo.wvfs.isfileorlink(f)
62 and repo.wvfs.isfileorlink(f)
63 and repo.dirstate.normalize(f) not in repo.dirstate
63 and repo.dirstate.normalize(f) not in repo.dirstate
64 and mctx[f2].cmp(wctx[f])
64 and mctx[f2].cmp(wctx[f])
65 )
65 )
66
66
67
67
68 class _unknowndirschecker:
68 class _unknowndirschecker:
69 """
69 """
70 Look for any unknown files or directories that may have a path conflict
70 Look for any unknown files or directories that may have a path conflict
71 with a file. If any path prefix of the file exists as a file or link,
71 with a file. If any path prefix of the file exists as a file or link,
72 then it conflicts. If the file itself is a directory that contains any
72 then it conflicts. If the file itself is a directory that contains any
73 file that is not tracked, then it conflicts.
73 file that is not tracked, then it conflicts.
74
74
75 Returns the shortest path at which a conflict occurs, or None if there is
75 Returns the shortest path at which a conflict occurs, or None if there is
76 no conflict.
76 no conflict.
77 """
77 """
78
78
79 def __init__(self):
79 def __init__(self):
80 # A set of paths known to be good. This prevents repeated checking of
80 # A set of paths known to be good. This prevents repeated checking of
81 # dirs. It will be updated with any new dirs that are checked and found
81 # dirs. It will be updated with any new dirs that are checked and found
82 # to be safe.
82 # to be safe.
83 self._unknowndircache = set()
83 self._unknowndircache = set()
84
84
85 # A set of paths that are known to be absent. This prevents repeated
85 # A set of paths that are known to be absent. This prevents repeated
86 # checking of subdirectories that are known not to exist. It will be
86 # checking of subdirectories that are known not to exist. It will be
87 # updated with any new dirs that are checked and found to be absent.
87 # updated with any new dirs that are checked and found to be absent.
88 self._missingdircache = set()
88 self._missingdircache = set()
89
89
90 def __call__(self, repo, wctx, f):
90 def __call__(self, repo, wctx, f):
91 if wctx.isinmemory():
91 if wctx.isinmemory():
92 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
92 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
93 return False
93 return False
94
94
95 # Check for path prefixes that exist as unknown files.
95 # Check for path prefixes that exist as unknown files.
96 for p in reversed(list(pathutil.finddirs(f))):
96 for p in reversed(list(pathutil.finddirs(f))):
97 if p in self._missingdircache:
97 if p in self._missingdircache:
98 return
98 return
99 if p in self._unknowndircache:
99 if p in self._unknowndircache:
100 continue
100 continue
101 if repo.wvfs.audit.check(p):
101 if repo.wvfs.audit.check(p):
102 if (
102 if (
103 repo.wvfs.isfileorlink(p)
103 repo.wvfs.isfileorlink(p)
104 and repo.dirstate.normalize(p) not in repo.dirstate
104 and repo.dirstate.normalize(p) not in repo.dirstate
105 ):
105 ):
106 return p
106 return p
107 if not repo.wvfs.lexists(p):
107 if not repo.wvfs.lexists(p):
108 self._missingdircache.add(p)
108 self._missingdircache.add(p)
109 return
109 return
110 self._unknowndircache.add(p)
110 self._unknowndircache.add(p)
111
111
112 # Check if the file conflicts with a directory containing unknown files.
112 # Check if the file conflicts with a directory containing unknown files.
113 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
113 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
114 # Does the directory contain any files that are not in the dirstate?
114 # Does the directory contain any files that are not in the dirstate?
115 for p, dirs, files in repo.wvfs.walk(f):
115 for p, dirs, files in repo.wvfs.walk(f):
116 for fn in files:
116 for fn in files:
117 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
117 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
118 relf = repo.dirstate.normalize(relf, isknown=True)
118 relf = repo.dirstate.normalize(relf, isknown=True)
119 if relf not in repo.dirstate:
119 if relf not in repo.dirstate:
120 return f
120 return f
121 return None
121 return None
122
122
123
123
124 def _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce):
124 def _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce):
125 """
125 """
126 Considers any actions that care about the presence of conflicting unknown
126 Considers any actions that care about the presence of conflicting unknown
127 files. For some actions, the result is to abort; for others, it is to
127 files. For some actions, the result is to abort; for others, it is to
128 choose a different action.
128 choose a different action.
129 """
129 """
130 fileconflicts = set()
130 fileconflicts = set()
131 pathconflicts = set()
131 pathconflicts = set()
132 warnconflicts = set()
132 warnconflicts = set()
133 abortconflicts = set()
133 abortconflicts = set()
134 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
134 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
135 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
135 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
136 pathconfig = repo.ui.configbool(
136 pathconfig = repo.ui.configbool(
137 b'experimental', b'merge.checkpathconflicts'
137 b'experimental', b'merge.checkpathconflicts'
138 )
138 )
139 if not force:
139 if not force:
140
140
141 def collectconflicts(conflicts, config):
141 def collectconflicts(conflicts, config):
142 if config == b'abort':
142 if config == b'abort':
143 abortconflicts.update(conflicts)
143 abortconflicts.update(conflicts)
144 elif config == b'warn':
144 elif config == b'warn':
145 warnconflicts.update(conflicts)
145 warnconflicts.update(conflicts)
146
146
147 checkunknowndirs = _unknowndirschecker()
147 checkunknowndirs = _unknowndirschecker()
148 for f in mresult.files(
148 for f in mresult.files(
149 (
149 (
150 mergestatemod.ACTION_CREATED,
150 mergestatemod.ACTION_CREATED,
151 mergestatemod.ACTION_DELETED_CHANGED,
151 mergestatemod.ACTION_DELETED_CHANGED,
152 )
152 )
153 ):
153 ):
154 if _checkunknownfile(repo, wctx, mctx, f):
154 if _checkunknownfile(repo, wctx, mctx, f):
155 fileconflicts.add(f)
155 fileconflicts.add(f)
156 elif pathconfig and f not in wctx:
156 elif pathconfig and f not in wctx:
157 path = checkunknowndirs(repo, wctx, f)
157 path = checkunknowndirs(repo, wctx, f)
158 if path is not None:
158 if path is not None:
159 pathconflicts.add(path)
159 pathconflicts.add(path)
160 for f, args, msg in mresult.getactions(
160 for f, args, msg in mresult.getactions(
161 [mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]
161 [mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]
162 ):
162 ):
163 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
163 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
164 fileconflicts.add(f)
164 fileconflicts.add(f)
165
165
166 allconflicts = fileconflicts | pathconflicts
166 allconflicts = fileconflicts | pathconflicts
167 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
167 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
168 unknownconflicts = allconflicts - ignoredconflicts
168 unknownconflicts = allconflicts - ignoredconflicts
169 collectconflicts(ignoredconflicts, ignoredconfig)
169 collectconflicts(ignoredconflicts, ignoredconfig)
170 collectconflicts(unknownconflicts, unknownconfig)
170 collectconflicts(unknownconflicts, unknownconfig)
171 else:
171 else:
172 for f, args, msg in list(
172 for f, args, msg in list(
173 mresult.getactions([mergestatemod.ACTION_CREATED_MERGE])
173 mresult.getactions([mergestatemod.ACTION_CREATED_MERGE])
174 ):
174 ):
175 fl2, anc = args
175 fl2, anc = args
176 different = _checkunknownfile(repo, wctx, mctx, f)
176 different = _checkunknownfile(repo, wctx, mctx, f)
177 if repo.dirstate._ignore(f):
177 if repo.dirstate._ignore(f):
178 config = ignoredconfig
178 config = ignoredconfig
179 else:
179 else:
180 config = unknownconfig
180 config = unknownconfig
181
181
182 # The behavior when force is True is described by this table:
182 # The behavior when force is True is described by this table:
183 # config different mergeforce | action backup
183 # config different mergeforce | action backup
184 # * n * | get n
184 # * n * | get n
185 # * y y | merge -
185 # * y y | merge -
186 # abort y n | merge - (1)
186 # abort y n | merge - (1)
187 # warn y n | warn + get y
187 # warn y n | warn + get y
188 # ignore y n | get y
188 # ignore y n | get y
189 #
189 #
190 # (1) this is probably the wrong behavior here -- we should
190 # (1) this is probably the wrong behavior here -- we should
191 # probably abort, but some actions like rebases currently
191 # probably abort, but some actions like rebases currently
192 # don't like an abort happening in the middle of
192 # don't like an abort happening in the middle of
193 # merge.update.
193 # merge.update.
194 if not different:
194 if not different:
195 mresult.addfile(
195 mresult.addfile(
196 f,
196 f,
197 mergestatemod.ACTION_GET,
197 mergestatemod.ACTION_GET,
198 (fl2, False),
198 (fl2, False),
199 b'remote created',
199 b'remote created',
200 )
200 )
201 elif mergeforce or config == b'abort':
201 elif mergeforce or config == b'abort':
202 mresult.addfile(
202 mresult.addfile(
203 f,
203 f,
204 mergestatemod.ACTION_MERGE,
204 mergestatemod.ACTION_MERGE,
205 (f, f, None, False, anc),
205 (f, f, None, False, anc),
206 b'remote differs from untracked local',
206 b'remote differs from untracked local',
207 )
207 )
208 elif config == b'abort':
208 elif config == b'abort':
209 abortconflicts.add(f)
209 abortconflicts.add(f)
210 else:
210 else:
211 if config == b'warn':
211 if config == b'warn':
212 warnconflicts.add(f)
212 warnconflicts.add(f)
213 mresult.addfile(
213 mresult.addfile(
214 f,
214 f,
215 mergestatemod.ACTION_GET,
215 mergestatemod.ACTION_GET,
216 (fl2, True),
216 (fl2, True),
217 b'remote created',
217 b'remote created',
218 )
218 )
219
219
220 for f in sorted(abortconflicts):
220 for f in sorted(abortconflicts):
221 warn = repo.ui.warn
221 warn = repo.ui.warn
222 if f in pathconflicts:
222 if f in pathconflicts:
223 if repo.wvfs.isfileorlink(f):
223 if repo.wvfs.isfileorlink(f):
224 warn(_(b"%s: untracked file conflicts with directory\n") % f)
224 warn(_(b"%s: untracked file conflicts with directory\n") % f)
225 else:
225 else:
226 warn(_(b"%s: untracked directory conflicts with file\n") % f)
226 warn(_(b"%s: untracked directory conflicts with file\n") % f)
227 else:
227 else:
228 warn(_(b"%s: untracked file differs\n") % f)
228 warn(_(b"%s: untracked file differs\n") % f)
229 if abortconflicts:
229 if abortconflicts:
230 raise error.StateError(
230 raise error.StateError(
231 _(
231 _(
232 b"untracked files in working directory "
232 b"untracked files in working directory "
233 b"differ from files in requested revision"
233 b"differ from files in requested revision"
234 )
234 )
235 )
235 )
236
236
237 for f in sorted(warnconflicts):
237 for f in sorted(warnconflicts):
238 if repo.wvfs.isfileorlink(f):
238 if repo.wvfs.isfileorlink(f):
239 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
239 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
240 else:
240 else:
241 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
241 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
242
242
243 for f, args, msg in list(
243 for f, args, msg in list(
244 mresult.getactions([mergestatemod.ACTION_CREATED])
244 mresult.getactions([mergestatemod.ACTION_CREATED])
245 ):
245 ):
246 backup = (
246 backup = (
247 f in fileconflicts
247 f in fileconflicts
248 or f in pathconflicts
248 or pathconflicts
249 or any(p in pathconflicts for p in pathutil.finddirs(f))
249 and (
250 f in pathconflicts
251 or any(p in pathconflicts for p in pathutil.finddirs(f))
252 )
250 )
253 )
251 (flags,) = args
254 (flags,) = args
252 mresult.addfile(f, mergestatemod.ACTION_GET, (flags, backup), msg)
255 mresult.addfile(f, mergestatemod.ACTION_GET, (flags, backup), msg)
253
256
254
257
255 def _forgetremoved(wctx, mctx, branchmerge, mresult):
258 def _forgetremoved(wctx, mctx, branchmerge, mresult):
256 """
259 """
257 Forget removed files
260 Forget removed files
258
261
259 If we're jumping between revisions (as opposed to merging), and if
262 If we're jumping between revisions (as opposed to merging), and if
260 neither the working directory nor the target rev has the file,
263 neither the working directory nor the target rev has the file,
261 then we need to remove it from the dirstate, to prevent the
264 then we need to remove it from the dirstate, to prevent the
262 dirstate from listing the file when it is no longer in the
265 dirstate from listing the file when it is no longer in the
263 manifest.
266 manifest.
264
267
265 If we're merging, and the other revision has removed a file
268 If we're merging, and the other revision has removed a file
266 that is not present in the working directory, we need to mark it
269 that is not present in the working directory, we need to mark it
267 as removed.
270 as removed.
268 """
271 """
269
272
270 m = mergestatemod.ACTION_FORGET
273 m = mergestatemod.ACTION_FORGET
271 if branchmerge:
274 if branchmerge:
272 m = mergestatemod.ACTION_REMOVE
275 m = mergestatemod.ACTION_REMOVE
273 for f in wctx.deleted():
276 for f in wctx.deleted():
274 if f not in mctx:
277 if f not in mctx:
275 mresult.addfile(f, m, None, b"forget deleted")
278 mresult.addfile(f, m, None, b"forget deleted")
276
279
277 if not branchmerge:
280 if not branchmerge:
278 for f in wctx.removed():
281 for f in wctx.removed():
279 if f not in mctx:
282 if f not in mctx:
280 mresult.addfile(
283 mresult.addfile(
281 f,
284 f,
282 mergestatemod.ACTION_FORGET,
285 mergestatemod.ACTION_FORGET,
283 None,
286 None,
284 b"forget removed",
287 b"forget removed",
285 )
288 )
286
289
287
290
288 def _checkcollision(repo, wmf, mresult):
291 def _checkcollision(repo, wmf, mresult):
289 """
292 """
290 Check for case-folding collisions.
293 Check for case-folding collisions.
291 """
294 """
292 # If the repo is narrowed, filter out files outside the narrowspec.
295 # If the repo is narrowed, filter out files outside the narrowspec.
293 narrowmatch = repo.narrowmatch()
296 narrowmatch = repo.narrowmatch()
294 if not narrowmatch.always():
297 if not narrowmatch.always():
295 pmmf = set(wmf.walk(narrowmatch))
298 pmmf = set(wmf.walk(narrowmatch))
296 if mresult:
299 if mresult:
297 for f in list(mresult.files()):
300 for f in list(mresult.files()):
298 if not narrowmatch(f):
301 if not narrowmatch(f):
299 mresult.removefile(f)
302 mresult.removefile(f)
300 else:
303 else:
301 # build provisional merged manifest up
304 # build provisional merged manifest up
302 pmmf = set(wmf)
305 pmmf = set(wmf)
303
306
304 if mresult:
307 if mresult:
305 # KEEP and EXEC are no-op
308 # KEEP and EXEC are no-op
306 for f in mresult.files(
309 for f in mresult.files(
307 (
310 (
308 mergestatemod.ACTION_ADD,
311 mergestatemod.ACTION_ADD,
309 mergestatemod.ACTION_ADD_MODIFIED,
312 mergestatemod.ACTION_ADD_MODIFIED,
310 mergestatemod.ACTION_FORGET,
313 mergestatemod.ACTION_FORGET,
311 mergestatemod.ACTION_GET,
314 mergestatemod.ACTION_GET,
312 mergestatemod.ACTION_CHANGED_DELETED,
315 mergestatemod.ACTION_CHANGED_DELETED,
313 mergestatemod.ACTION_DELETED_CHANGED,
316 mergestatemod.ACTION_DELETED_CHANGED,
314 )
317 )
315 ):
318 ):
316 pmmf.add(f)
319 pmmf.add(f)
317 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
320 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
318 pmmf.discard(f)
321 pmmf.discard(f)
319 for f, args, msg in mresult.getactions(
322 for f, args, msg in mresult.getactions(
320 [mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]
323 [mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]
321 ):
324 ):
322 f2, flags = args
325 f2, flags = args
323 pmmf.discard(f2)
326 pmmf.discard(f2)
324 pmmf.add(f)
327 pmmf.add(f)
325 for f in mresult.files((mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,)):
328 for f in mresult.files((mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,)):
326 pmmf.add(f)
329 pmmf.add(f)
327 for f, args, msg in mresult.getactions([mergestatemod.ACTION_MERGE]):
330 for f, args, msg in mresult.getactions([mergestatemod.ACTION_MERGE]):
328 f1, f2, fa, move, anc = args
331 f1, f2, fa, move, anc = args
329 if move:
332 if move:
330 pmmf.discard(f1)
333 pmmf.discard(f1)
331 pmmf.add(f)
334 pmmf.add(f)
332
335
333 # check case-folding collision in provisional merged manifest
336 # check case-folding collision in provisional merged manifest
334 foldmap = {}
337 foldmap = {}
335 for f in pmmf:
338 for f in pmmf:
336 fold = util.normcase(f)
339 fold = util.normcase(f)
337 if fold in foldmap:
340 if fold in foldmap:
338 msg = _(b"case-folding collision between %s and %s")
341 msg = _(b"case-folding collision between %s and %s")
339 msg %= (f, foldmap[fold])
342 msg %= (f, foldmap[fold])
340 raise error.StateError(msg)
343 raise error.StateError(msg)
341 foldmap[fold] = f
344 foldmap[fold] = f
342
345
343 # check case-folding of directories
346 # check case-folding of directories
344 foldprefix = unfoldprefix = lastfull = b''
347 foldprefix = unfoldprefix = lastfull = b''
345 for fold, f in sorted(foldmap.items()):
348 for fold, f in sorted(foldmap.items()):
346 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
349 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
347 # the folded prefix matches but actual casing is different
350 # the folded prefix matches but actual casing is different
348 msg = _(b"case-folding collision between %s and directory of %s")
351 msg = _(b"case-folding collision between %s and directory of %s")
349 msg %= (lastfull, f)
352 msg %= (lastfull, f)
350 raise error.StateError(msg)
353 raise error.StateError(msg)
351 foldprefix = fold + b'/'
354 foldprefix = fold + b'/'
352 unfoldprefix = f + b'/'
355 unfoldprefix = f + b'/'
353 lastfull = f
356 lastfull = f
354
357
355
358
356 def _filesindirs(repo, manifest, dirs):
359 def _filesindirs(repo, manifest, dirs):
357 """
360 """
358 Generator that yields pairs of all the files in the manifest that are found
361 Generator that yields pairs of all the files in the manifest that are found
359 inside the directories listed in dirs, and which directory they are found
362 inside the directories listed in dirs, and which directory they are found
360 in.
363 in.
361 """
364 """
362 for f in manifest:
365 for f in manifest:
363 for p in pathutil.finddirs(f):
366 for p in pathutil.finddirs(f):
364 if p in dirs:
367 if p in dirs:
365 yield f, p
368 yield f, p
366 break
369 break
367
370
368
371
369 def checkpathconflicts(repo, wctx, mctx, mresult):
372 def checkpathconflicts(repo, wctx, mctx, mresult):
370 """
373 """
371 Check if any actions introduce path conflicts in the repository, updating
374 Check if any actions introduce path conflicts in the repository, updating
372 actions to record or handle the path conflict accordingly.
375 actions to record or handle the path conflict accordingly.
373 """
376 """
374 mf = wctx.manifest()
377 mf = wctx.manifest()
375
378
376 # The set of local files that conflict with a remote directory.
379 # The set of local files that conflict with a remote directory.
377 localconflicts = set()
380 localconflicts = set()
378
381
379 # The set of directories that conflict with a remote file, and so may cause
382 # The set of directories that conflict with a remote file, and so may cause
380 # conflicts if they still contain any files after the merge.
383 # conflicts if they still contain any files after the merge.
381 remoteconflicts = set()
384 remoteconflicts = set()
382
385
383 # The set of directories that appear as both a file and a directory in the
386 # The set of directories that appear as both a file and a directory in the
384 # remote manifest. These indicate an invalid remote manifest, which
387 # remote manifest. These indicate an invalid remote manifest, which
385 # can't be updated to cleanly.
388 # can't be updated to cleanly.
386 invalidconflicts = set()
389 invalidconflicts = set()
387
390
388 # The set of directories that contain files that are being created.
391 # The set of directories that contain files that are being created.
389 createdfiledirs = set()
392 createdfiledirs = set()
390
393
391 # The set of files deleted by all the actions.
394 # The set of files deleted by all the actions.
392 deletedfiles = set()
395 deletedfiles = set()
393
396
394 for f in mresult.files(
397 for f in mresult.files(
395 (
398 (
396 mergestatemod.ACTION_CREATED,
399 mergestatemod.ACTION_CREATED,
397 mergestatemod.ACTION_DELETED_CHANGED,
400 mergestatemod.ACTION_DELETED_CHANGED,
398 mergestatemod.ACTION_MERGE,
401 mergestatemod.ACTION_MERGE,
399 mergestatemod.ACTION_CREATED_MERGE,
402 mergestatemod.ACTION_CREATED_MERGE,
400 )
403 )
401 ):
404 ):
402 # This action may create a new local file.
405 # This action may create a new local file.
403 createdfiledirs.update(pathutil.finddirs(f))
406 createdfiledirs.update(pathutil.finddirs(f))
404 if mf.hasdir(f):
407 if mf.hasdir(f):
405 # The file aliases a local directory. This might be ok if all
408 # The file aliases a local directory. This might be ok if all
406 # the files in the local directory are being deleted. This
409 # the files in the local directory are being deleted. This
407 # will be checked once we know what all the deleted files are.
410 # will be checked once we know what all the deleted files are.
408 remoteconflicts.add(f)
411 remoteconflicts.add(f)
409 # Track the names of all deleted files.
412 # Track the names of all deleted files.
410 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
413 for f in mresult.files((mergestatemod.ACTION_REMOVE,)):
411 deletedfiles.add(f)
414 deletedfiles.add(f)
412 for (f, args, msg) in mresult.getactions((mergestatemod.ACTION_MERGE,)):
415 for (f, args, msg) in mresult.getactions((mergestatemod.ACTION_MERGE,)):
413 f1, f2, fa, move, anc = args
416 f1, f2, fa, move, anc = args
414 if move:
417 if move:
415 deletedfiles.add(f1)
418 deletedfiles.add(f1)
416 for (f, args, msg) in mresult.getactions(
419 for (f, args, msg) in mresult.getactions(
417 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,)
420 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,)
418 ):
421 ):
419 f2, flags = args
422 f2, flags = args
420 deletedfiles.add(f2)
423 deletedfiles.add(f2)
421
424
422 # Check all directories that contain created files for path conflicts.
425 # Check all directories that contain created files for path conflicts.
423 for p in createdfiledirs:
426 for p in createdfiledirs:
424 if p in mf:
427 if p in mf:
425 if p in mctx:
428 if p in mctx:
426 # A file is in a directory which aliases both a local
429 # A file is in a directory which aliases both a local
427 # and a remote file. This is an internal inconsistency
430 # and a remote file. This is an internal inconsistency
428 # within the remote manifest.
431 # within the remote manifest.
429 invalidconflicts.add(p)
432 invalidconflicts.add(p)
430 else:
433 else:
431 # A file is in a directory which aliases a local file.
434 # A file is in a directory which aliases a local file.
432 # We will need to rename the local file.
435 # We will need to rename the local file.
433 localconflicts.add(p)
436 localconflicts.add(p)
434 pd = mresult.getfile(p)
437 pd = mresult.getfile(p)
435 if pd and pd[0] in (
438 if pd and pd[0] in (
436 mergestatemod.ACTION_CREATED,
439 mergestatemod.ACTION_CREATED,
437 mergestatemod.ACTION_DELETED_CHANGED,
440 mergestatemod.ACTION_DELETED_CHANGED,
438 mergestatemod.ACTION_MERGE,
441 mergestatemod.ACTION_MERGE,
439 mergestatemod.ACTION_CREATED_MERGE,
442 mergestatemod.ACTION_CREATED_MERGE,
440 ):
443 ):
441 # The file is in a directory which aliases a remote file.
444 # The file is in a directory which aliases a remote file.
442 # This is an internal inconsistency within the remote
445 # This is an internal inconsistency within the remote
443 # manifest.
446 # manifest.
444 invalidconflicts.add(p)
447 invalidconflicts.add(p)
445
448
446 # Rename all local conflicting files that have not been deleted.
449 # Rename all local conflicting files that have not been deleted.
447 for p in localconflicts:
450 for p in localconflicts:
448 if p not in deletedfiles:
451 if p not in deletedfiles:
449 ctxname = bytes(wctx).rstrip(b'+')
452 ctxname = bytes(wctx).rstrip(b'+')
450 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
453 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
451 porig = wctx[p].copysource() or p
454 porig = wctx[p].copysource() or p
452 mresult.addfile(
455 mresult.addfile(
453 pnew,
456 pnew,
454 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
457 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
455 (p, porig),
458 (p, porig),
456 b'local path conflict',
459 b'local path conflict',
457 )
460 )
458 mresult.addfile(
461 mresult.addfile(
459 p,
462 p,
460 mergestatemod.ACTION_PATH_CONFLICT,
463 mergestatemod.ACTION_PATH_CONFLICT,
461 (pnew, b'l'),
464 (pnew, b'l'),
462 b'path conflict',
465 b'path conflict',
463 )
466 )
464
467
465 if remoteconflicts:
468 if remoteconflicts:
466 # Check if all files in the conflicting directories have been removed.
469 # Check if all files in the conflicting directories have been removed.
467 ctxname = bytes(mctx).rstrip(b'+')
470 ctxname = bytes(mctx).rstrip(b'+')
468 for f, p in _filesindirs(repo, mf, remoteconflicts):
471 for f, p in _filesindirs(repo, mf, remoteconflicts):
469 if f not in deletedfiles:
472 if f not in deletedfiles:
470 m, args, msg = mresult.getfile(p)
473 m, args, msg = mresult.getfile(p)
471 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
474 pnew = util.safename(p, ctxname, wctx, set(mresult.files()))
472 if m in (
475 if m in (
473 mergestatemod.ACTION_DELETED_CHANGED,
476 mergestatemod.ACTION_DELETED_CHANGED,
474 mergestatemod.ACTION_MERGE,
477 mergestatemod.ACTION_MERGE,
475 ):
478 ):
476 # Action was merge, just update target.
479 # Action was merge, just update target.
477 mresult.addfile(pnew, m, args, msg)
480 mresult.addfile(pnew, m, args, msg)
478 else:
481 else:
479 # Action was create, change to renamed get action.
482 # Action was create, change to renamed get action.
480 fl = args[0]
483 fl = args[0]
481 mresult.addfile(
484 mresult.addfile(
482 pnew,
485 pnew,
483 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
486 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
484 (p, fl),
487 (p, fl),
485 b'remote path conflict',
488 b'remote path conflict',
486 )
489 )
487 mresult.addfile(
490 mresult.addfile(
488 p,
491 p,
489 mergestatemod.ACTION_PATH_CONFLICT,
492 mergestatemod.ACTION_PATH_CONFLICT,
490 (pnew, b'r'),
493 (pnew, b'r'),
491 b'path conflict',
494 b'path conflict',
492 )
495 )
493 remoteconflicts.remove(p)
496 remoteconflicts.remove(p)
494 break
497 break
495
498
496 if invalidconflicts:
499 if invalidconflicts:
497 for p in invalidconflicts:
500 for p in invalidconflicts:
498 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
501 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
499 raise error.StateError(
502 raise error.StateError(
500 _(b"destination manifest contains path conflicts")
503 _(b"destination manifest contains path conflicts")
501 )
504 )
502
505
503
506
504 def _filternarrowactions(narrowmatch, branchmerge, mresult):
507 def _filternarrowactions(narrowmatch, branchmerge, mresult):
505 """
508 """
506 Filters out actions that can ignored because the repo is narrowed.
509 Filters out actions that can ignored because the repo is narrowed.
507
510
508 Raise an exception if the merge cannot be completed because the repo is
511 Raise an exception if the merge cannot be completed because the repo is
509 narrowed.
512 narrowed.
510 """
513 """
511 # We mutate the items in the dict during iteration, so iterate
514 # We mutate the items in the dict during iteration, so iterate
512 # over a copy.
515 # over a copy.
513 for f, action in mresult.filemap():
516 for f, action in mresult.filemap():
514 if narrowmatch(f):
517 if narrowmatch(f):
515 pass
518 pass
516 elif not branchmerge:
519 elif not branchmerge:
517 mresult.removefile(f) # just updating, ignore changes outside clone
520 mresult.removefile(f) # just updating, ignore changes outside clone
518 elif action[0].no_op:
521 elif action[0].no_op:
519 mresult.removefile(f) # merge does not affect file
522 mresult.removefile(f) # merge does not affect file
520 elif action[0].narrow_safe:
523 elif action[0].narrow_safe:
521 if not f.endswith(b'/'):
524 if not f.endswith(b'/'):
522 mresult.removefile(f) # merge won't affect on-disk files
525 mresult.removefile(f) # merge won't affect on-disk files
523
526
524 mresult.addcommitinfo(
527 mresult.addcommitinfo(
525 f, b'outside-narrow-merge-action', action[0].changes
528 f, b'outside-narrow-merge-action', action[0].changes
526 )
529 )
527 else: # TODO: handle the tree case
530 else: # TODO: handle the tree case
528 msg = _(
531 msg = _(
529 b'merge affects file \'%s\' outside narrow, '
532 b'merge affects file \'%s\' outside narrow, '
530 b'which is not yet supported'
533 b'which is not yet supported'
531 )
534 )
532 hint = _(b'merging in the other direction may work')
535 hint = _(b'merging in the other direction may work')
533 raise error.Abort(msg % f, hint=hint)
536 raise error.Abort(msg % f, hint=hint)
534 else:
537 else:
535 msg = _(b'conflict in file \'%s\' is outside narrow clone')
538 msg = _(b'conflict in file \'%s\' is outside narrow clone')
536 raise error.StateError(msg % f)
539 raise error.StateError(msg % f)
537
540
538
541
539 class mergeresult:
542 class mergeresult:
540 """An object representing result of merging manifests.
543 """An object representing result of merging manifests.
541
544
542 It has information about what actions need to be performed on dirstate
545 It has information about what actions need to be performed on dirstate
543 mapping of divergent renames and other such cases."""
546 mapping of divergent renames and other such cases."""
544
547
545 def __init__(self):
548 def __init__(self):
546 """
549 """
547 filemapping: dict of filename as keys and action related info as values
550 filemapping: dict of filename as keys and action related info as values
548 diverge: mapping of source name -> list of dest name for
551 diverge: mapping of source name -> list of dest name for
549 divergent renames
552 divergent renames
550 renamedelete: mapping of source name -> list of destinations for files
553 renamedelete: mapping of source name -> list of destinations for files
551 deleted on one side and renamed on other.
554 deleted on one side and renamed on other.
552 commitinfo: dict containing data which should be used on commit
555 commitinfo: dict containing data which should be used on commit
553 contains a filename -> info mapping
556 contains a filename -> info mapping
554 actionmapping: dict of action names as keys and values are dict of
557 actionmapping: dict of action names as keys and values are dict of
555 filename as key and related data as values
558 filename as key and related data as values
556 """
559 """
557 self._filemapping = {}
560 self._filemapping = {}
558 self._diverge = {}
561 self._diverge = {}
559 self._renamedelete = {}
562 self._renamedelete = {}
560 self._commitinfo = collections.defaultdict(dict)
563 self._commitinfo = collections.defaultdict(dict)
561 self._actionmapping = collections.defaultdict(dict)
564 self._actionmapping = collections.defaultdict(dict)
562
565
563 def updatevalues(self, diverge, renamedelete):
566 def updatevalues(self, diverge, renamedelete):
564 self._diverge = diverge
567 self._diverge = diverge
565 self._renamedelete = renamedelete
568 self._renamedelete = renamedelete
566
569
567 def addfile(self, filename, action, data, message):
570 def addfile(self, filename, action, data, message):
568 """adds a new file to the mergeresult object
571 """adds a new file to the mergeresult object
569
572
570 filename: file which we are adding
573 filename: file which we are adding
571 action: one of mergestatemod.ACTION_*
574 action: one of mergestatemod.ACTION_*
572 data: a tuple of information like fctx and ctx related to this merge
575 data: a tuple of information like fctx and ctx related to this merge
573 message: a message about the merge
576 message: a message about the merge
574 """
577 """
575 # if the file already existed, we need to delete it's old
578 # if the file already existed, we need to delete it's old
576 # entry form _actionmapping too
579 # entry form _actionmapping too
577 if filename in self._filemapping:
580 if filename in self._filemapping:
578 a, d, m = self._filemapping[filename]
581 a, d, m = self._filemapping[filename]
579 del self._actionmapping[a][filename]
582 del self._actionmapping[a][filename]
580
583
581 self._filemapping[filename] = (action, data, message)
584 self._filemapping[filename] = (action, data, message)
582 self._actionmapping[action][filename] = (data, message)
585 self._actionmapping[action][filename] = (data, message)
583
586
584 def getfile(self, filename, default_return=None):
587 def getfile(self, filename, default_return=None):
585 """returns (action, args, msg) about this file
588 """returns (action, args, msg) about this file
586
589
587 returns default_return if the file is not present"""
590 returns default_return if the file is not present"""
588 if filename in self._filemapping:
591 if filename in self._filemapping:
589 return self._filemapping[filename]
592 return self._filemapping[filename]
590 return default_return
593 return default_return
591
594
592 def files(self, actions=None):
595 def files(self, actions=None):
593 """returns files on which provided action needs to perfromed
596 """returns files on which provided action needs to perfromed
594
597
595 If actions is None, all files are returned
598 If actions is None, all files are returned
596 """
599 """
597 # TODO: think whether we should return renamedelete and
600 # TODO: think whether we should return renamedelete and
598 # diverge filenames also
601 # diverge filenames also
599 if actions is None:
602 if actions is None:
600 for f in self._filemapping:
603 for f in self._filemapping:
601 yield f
604 yield f
602
605
603 else:
606 else:
604 for a in actions:
607 for a in actions:
605 for f in self._actionmapping[a]:
608 for f in self._actionmapping[a]:
606 yield f
609 yield f
607
610
608 def removefile(self, filename):
611 def removefile(self, filename):
609 """removes a file from the mergeresult object as the file might
612 """removes a file from the mergeresult object as the file might
610 not merging anymore"""
613 not merging anymore"""
611 action, data, message = self._filemapping[filename]
614 action, data, message = self._filemapping[filename]
612 del self._filemapping[filename]
615 del self._filemapping[filename]
613 del self._actionmapping[action][filename]
616 del self._actionmapping[action][filename]
614
617
615 def getactions(self, actions, sort=False):
618 def getactions(self, actions, sort=False):
616 """get list of files which are marked with these actions
619 """get list of files which are marked with these actions
617 if sort is true, files for each action is sorted and then added
620 if sort is true, files for each action is sorted and then added
618
621
619 Returns a list of tuple of form (filename, data, message)
622 Returns a list of tuple of form (filename, data, message)
620 """
623 """
621 for a in actions:
624 for a in actions:
622 if sort:
625 if sort:
623 for f in sorted(self._actionmapping[a]):
626 for f in sorted(self._actionmapping[a]):
624 args, msg = self._actionmapping[a][f]
627 args, msg = self._actionmapping[a][f]
625 yield f, args, msg
628 yield f, args, msg
626 else:
629 else:
627 for f, (args, msg) in self._actionmapping[a].items():
630 for f, (args, msg) in self._actionmapping[a].items():
628 yield f, args, msg
631 yield f, args, msg
629
632
630 def len(self, actions=None):
633 def len(self, actions=None):
631 """returns number of files which needs actions
634 """returns number of files which needs actions
632
635
633 if actions is passed, total of number of files in that action
636 if actions is passed, total of number of files in that action
634 only is returned"""
637 only is returned"""
635
638
636 if actions is None:
639 if actions is None:
637 return len(self._filemapping)
640 return len(self._filemapping)
638
641
639 return sum(len(self._actionmapping[a]) for a in actions)
642 return sum(len(self._actionmapping[a]) for a in actions)
640
643
641 def filemap(self, sort=False):
644 def filemap(self, sort=False):
642 if sorted:
645 if sorted:
643 for key, val in sorted(self._filemapping.items()):
646 for key, val in sorted(self._filemapping.items()):
644 yield key, val
647 yield key, val
645 else:
648 else:
646 for key, val in self._filemapping.items():
649 for key, val in self._filemapping.items():
647 yield key, val
650 yield key, val
648
651
649 def addcommitinfo(self, filename, key, value):
652 def addcommitinfo(self, filename, key, value):
650 """adds key-value information about filename which will be required
653 """adds key-value information about filename which will be required
651 while committing this merge"""
654 while committing this merge"""
652 self._commitinfo[filename][key] = value
655 self._commitinfo[filename][key] = value
653
656
654 @property
657 @property
655 def diverge(self):
658 def diverge(self):
656 return self._diverge
659 return self._diverge
657
660
658 @property
661 @property
659 def renamedelete(self):
662 def renamedelete(self):
660 return self._renamedelete
663 return self._renamedelete
661
664
662 @property
665 @property
663 def commitinfo(self):
666 def commitinfo(self):
664 return self._commitinfo
667 return self._commitinfo
665
668
666 @property
669 @property
667 def actionsdict(self):
670 def actionsdict(self):
668 """returns a dictionary of actions to be perfomed with action as key
671 """returns a dictionary of actions to be perfomed with action as key
669 and a list of files and related arguments as values"""
672 and a list of files and related arguments as values"""
670 res = collections.defaultdict(list)
673 res = collections.defaultdict(list)
671 for a, d in self._actionmapping.items():
674 for a, d in self._actionmapping.items():
672 for f, (args, msg) in d.items():
675 for f, (args, msg) in d.items():
673 res[a].append((f, args, msg))
676 res[a].append((f, args, msg))
674 return res
677 return res
675
678
676 def setactions(self, actions):
679 def setactions(self, actions):
677 self._filemapping = actions
680 self._filemapping = actions
678 self._actionmapping = collections.defaultdict(dict)
681 self._actionmapping = collections.defaultdict(dict)
679 for f, (act, data, msg) in self._filemapping.items():
682 for f, (act, data, msg) in self._filemapping.items():
680 self._actionmapping[act][f] = data, msg
683 self._actionmapping[act][f] = data, msg
681
684
682 def hasconflicts(self):
685 def hasconflicts(self):
683 """tells whether this merge resulted in some actions which can
686 """tells whether this merge resulted in some actions which can
684 result in conflicts or not"""
687 result in conflicts or not"""
685 for a in self._actionmapping.keys():
688 for a in self._actionmapping.keys():
686 if (
689 if (
687 a
690 a
688 not in (
691 not in (
689 mergestatemod.ACTION_GET,
692 mergestatemod.ACTION_GET,
690 mergestatemod.ACTION_EXEC,
693 mergestatemod.ACTION_EXEC,
691 mergestatemod.ACTION_REMOVE,
694 mergestatemod.ACTION_REMOVE,
692 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
695 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
693 )
696 )
694 and self._actionmapping[a]
697 and self._actionmapping[a]
695 and not a.no_op
698 and not a.no_op
696 ):
699 ):
697 return True
700 return True
698
701
699 return False
702 return False
700
703
701
704
702 def manifestmerge(
705 def manifestmerge(
703 repo,
706 repo,
704 wctx,
707 wctx,
705 p2,
708 p2,
706 pa,
709 pa,
707 branchmerge,
710 branchmerge,
708 force,
711 force,
709 matcher,
712 matcher,
710 acceptremote,
713 acceptremote,
711 followcopies,
714 followcopies,
712 forcefulldiff=False,
715 forcefulldiff=False,
713 ):
716 ):
714 """
717 """
715 Merge wctx and p2 with ancestor pa and generate merge action list
718 Merge wctx and p2 with ancestor pa and generate merge action list
716
719
717 branchmerge and force are as passed in to update
720 branchmerge and force are as passed in to update
718 matcher = matcher to filter file lists
721 matcher = matcher to filter file lists
719 acceptremote = accept the incoming changes without prompting
722 acceptremote = accept the incoming changes without prompting
720
723
721 Returns an object of mergeresult class
724 Returns an object of mergeresult class
722 """
725 """
723 mresult = mergeresult()
726 mresult = mergeresult()
724 if matcher is not None and matcher.always():
727 if matcher is not None and matcher.always():
725 matcher = None
728 matcher = None
726
729
727 # manifests fetched in order are going to be faster, so prime the caches
730 # manifests fetched in order are going to be faster, so prime the caches
728 [
731 [
729 x.manifest()
732 x.manifest()
730 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
733 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
731 ]
734 ]
732
735
733 branch_copies1 = copies.branch_copies()
736 branch_copies1 = copies.branch_copies()
734 branch_copies2 = copies.branch_copies()
737 branch_copies2 = copies.branch_copies()
735 diverge = {}
738 diverge = {}
736 # information from merge which is needed at commit time
739 # information from merge which is needed at commit time
737 # for example choosing filelog of which parent to commit
740 # for example choosing filelog of which parent to commit
738 # TODO: use specific constants in future for this mapping
741 # TODO: use specific constants in future for this mapping
739 if followcopies:
742 if followcopies:
740 branch_copies1, branch_copies2, diverge = copies.mergecopies(
743 branch_copies1, branch_copies2, diverge = copies.mergecopies(
741 repo, wctx, p2, pa
744 repo, wctx, p2, pa
742 )
745 )
743
746
744 boolbm = pycompat.bytestr(bool(branchmerge))
747 boolbm = pycompat.bytestr(bool(branchmerge))
745 boolf = pycompat.bytestr(bool(force))
748 boolf = pycompat.bytestr(bool(force))
746 boolm = pycompat.bytestr(bool(matcher))
749 boolm = pycompat.bytestr(bool(matcher))
747 repo.ui.note(_(b"resolving manifests\n"))
750 repo.ui.note(_(b"resolving manifests\n"))
748 repo.ui.debug(
751 repo.ui.debug(
749 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
752 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
750 )
753 )
751 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
754 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
752
755
753 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
756 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
754 copied1 = set(branch_copies1.copy.values())
757 copied1 = set(branch_copies1.copy.values())
755 copied1.update(branch_copies1.movewithdir.values())
758 copied1.update(branch_copies1.movewithdir.values())
756 copied2 = set(branch_copies2.copy.values())
759 copied2 = set(branch_copies2.copy.values())
757 copied2.update(branch_copies2.movewithdir.values())
760 copied2.update(branch_copies2.movewithdir.values())
758
761
759 if b'.hgsubstate' in m1 and wctx.rev() is None:
762 if b'.hgsubstate' in m1 and wctx.rev() is None:
760 # Check whether sub state is modified, and overwrite the manifest
763 # Check whether sub state is modified, and overwrite the manifest
761 # to flag the change. If wctx is a committed revision, we shouldn't
764 # to flag the change. If wctx is a committed revision, we shouldn't
762 # care for the dirty state of the working directory.
765 # care for the dirty state of the working directory.
763 if any(wctx.sub(s).dirty() for s in wctx.substate):
766 if any(wctx.sub(s).dirty() for s in wctx.substate):
764 m1[b'.hgsubstate'] = repo.nodeconstants.modifiednodeid
767 m1[b'.hgsubstate'] = repo.nodeconstants.modifiednodeid
765
768
766 # Don't use m2-vs-ma optimization if:
769 # Don't use m2-vs-ma optimization if:
767 # - ma is the same as m1 or m2, which we're just going to diff again later
770 # - ma is the same as m1 or m2, which we're just going to diff again later
768 # - The caller specifically asks for a full diff, which is useful during bid
771 # - The caller specifically asks for a full diff, which is useful during bid
769 # merge.
772 # merge.
770 # - we are tracking salvaged files specifically hence should process all
773 # - we are tracking salvaged files specifically hence should process all
771 # files
774 # files
772 if (
775 if (
773 pa not in ([wctx, p2] + wctx.parents())
776 pa not in ([wctx, p2] + wctx.parents())
774 and not forcefulldiff
777 and not forcefulldiff
775 and not (
778 and not (
776 repo.ui.configbool(b'experimental', b'merge-track-salvaged')
779 repo.ui.configbool(b'experimental', b'merge-track-salvaged')
777 or repo.filecopiesmode == b'changeset-sidedata'
780 or repo.filecopiesmode == b'changeset-sidedata'
778 )
781 )
779 ):
782 ):
780 # Identify which files are relevant to the merge, so we can limit the
783 # Identify which files are relevant to the merge, so we can limit the
781 # total m1-vs-m2 diff to just those files. This has significant
784 # total m1-vs-m2 diff to just those files. This has significant
782 # performance benefits in large repositories.
785 # performance benefits in large repositories.
783 relevantfiles = set(ma.diff(m2).keys())
786 relevantfiles = set(ma.diff(m2).keys())
784
787
785 # For copied and moved files, we need to add the source file too.
788 # For copied and moved files, we need to add the source file too.
786 for copykey, copyvalue in branch_copies1.copy.items():
789 for copykey, copyvalue in branch_copies1.copy.items():
787 if copyvalue in relevantfiles:
790 if copyvalue in relevantfiles:
788 relevantfiles.add(copykey)
791 relevantfiles.add(copykey)
789 for movedirkey in branch_copies1.movewithdir:
792 for movedirkey in branch_copies1.movewithdir:
790 relevantfiles.add(movedirkey)
793 relevantfiles.add(movedirkey)
791 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
794 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
792 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
795 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
793
796
794 diff = m1.diff(m2, match=matcher)
797 diff = m1.diff(m2, match=matcher)
795
798
796 for f, ((n1, fl1), (n2, fl2)) in diff.items():
799 for f, ((n1, fl1), (n2, fl2)) in diff.items():
797 if n1 and n2: # file exists on both local and remote side
800 if n1 and n2: # file exists on both local and remote side
798 if f not in ma:
801 if f not in ma:
799 # TODO: what if they're renamed from different sources?
802 # TODO: what if they're renamed from different sources?
800 fa = branch_copies1.copy.get(
803 fa = branch_copies1.copy.get(
801 f, None
804 f, None
802 ) or branch_copies2.copy.get(f, None)
805 ) or branch_copies2.copy.get(f, None)
803 args, msg = None, None
806 args, msg = None, None
804 if fa is not None:
807 if fa is not None:
805 args = (f, f, fa, False, pa.node())
808 args = (f, f, fa, False, pa.node())
806 msg = b'both renamed from %s' % fa
809 msg = b'both renamed from %s' % fa
807 else:
810 else:
808 args = (f, f, None, False, pa.node())
811 args = (f, f, None, False, pa.node())
809 msg = b'both created'
812 msg = b'both created'
810 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
813 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
811 elif f in branch_copies1.copy:
814 elif f in branch_copies1.copy:
812 fa = branch_copies1.copy[f]
815 fa = branch_copies1.copy[f]
813 mresult.addfile(
816 mresult.addfile(
814 f,
817 f,
815 mergestatemod.ACTION_MERGE,
818 mergestatemod.ACTION_MERGE,
816 (f, fa, fa, False, pa.node()),
819 (f, fa, fa, False, pa.node()),
817 b'local replaced from %s' % fa,
820 b'local replaced from %s' % fa,
818 )
821 )
819 elif f in branch_copies2.copy:
822 elif f in branch_copies2.copy:
820 fa = branch_copies2.copy[f]
823 fa = branch_copies2.copy[f]
821 mresult.addfile(
824 mresult.addfile(
822 f,
825 f,
823 mergestatemod.ACTION_MERGE,
826 mergestatemod.ACTION_MERGE,
824 (fa, f, fa, False, pa.node()),
827 (fa, f, fa, False, pa.node()),
825 b'other replaced from %s' % fa,
828 b'other replaced from %s' % fa,
826 )
829 )
827 else:
830 else:
828 a = ma[f]
831 a = ma[f]
829 fla = ma.flags(f)
832 fla = ma.flags(f)
830 nol = b'l' not in fl1 + fl2 + fla
833 nol = b'l' not in fl1 + fl2 + fla
831 if n2 == a and fl2 == fla:
834 if n2 == a and fl2 == fla:
832 mresult.addfile(
835 mresult.addfile(
833 f,
836 f,
834 mergestatemod.ACTION_KEEP,
837 mergestatemod.ACTION_KEEP,
835 (),
838 (),
836 b'remote unchanged',
839 b'remote unchanged',
837 )
840 )
838 elif n1 == a and fl1 == fla: # local unchanged - use remote
841 elif n1 == a and fl1 == fla: # local unchanged - use remote
839 if n1 == n2: # optimization: keep local content
842 if n1 == n2: # optimization: keep local content
840 mresult.addfile(
843 mresult.addfile(
841 f,
844 f,
842 mergestatemod.ACTION_EXEC,
845 mergestatemod.ACTION_EXEC,
843 (fl2,),
846 (fl2,),
844 b'update permissions',
847 b'update permissions',
845 )
848 )
846 else:
849 else:
847 mresult.addfile(
850 mresult.addfile(
848 f,
851 f,
849 mergestatemod.ACTION_GET,
852 mergestatemod.ACTION_GET,
850 (fl2, False),
853 (fl2, False),
851 b'remote is newer',
854 b'remote is newer',
852 )
855 )
853 if branchmerge:
856 if branchmerge:
854 mresult.addcommitinfo(
857 mresult.addcommitinfo(
855 f, b'filenode-source', b'other'
858 f, b'filenode-source', b'other'
856 )
859 )
857 elif nol and n2 == a: # remote only changed 'x'
860 elif nol and n2 == a: # remote only changed 'x'
858 mresult.addfile(
861 mresult.addfile(
859 f,
862 f,
860 mergestatemod.ACTION_EXEC,
863 mergestatemod.ACTION_EXEC,
861 (fl2,),
864 (fl2,),
862 b'update permissions',
865 b'update permissions',
863 )
866 )
864 elif nol and n1 == a: # local only changed 'x'
867 elif nol and n1 == a: # local only changed 'x'
865 mresult.addfile(
868 mresult.addfile(
866 f,
869 f,
867 mergestatemod.ACTION_GET,
870 mergestatemod.ACTION_GET,
868 (fl1, False),
871 (fl1, False),
869 b'remote is newer',
872 b'remote is newer',
870 )
873 )
871 if branchmerge:
874 if branchmerge:
872 mresult.addcommitinfo(f, b'filenode-source', b'other')
875 mresult.addcommitinfo(f, b'filenode-source', b'other')
873 else: # both changed something
876 else: # both changed something
874 mresult.addfile(
877 mresult.addfile(
875 f,
878 f,
876 mergestatemod.ACTION_MERGE,
879 mergestatemod.ACTION_MERGE,
877 (f, f, f, False, pa.node()),
880 (f, f, f, False, pa.node()),
878 b'versions differ',
881 b'versions differ',
879 )
882 )
880 elif n1: # file exists only on local side
883 elif n1: # file exists only on local side
881 if f in copied2:
884 if f in copied2:
882 pass # we'll deal with it on m2 side
885 pass # we'll deal with it on m2 side
883 elif (
886 elif (
884 f in branch_copies1.movewithdir
887 f in branch_copies1.movewithdir
885 ): # directory rename, move local
888 ): # directory rename, move local
886 f2 = branch_copies1.movewithdir[f]
889 f2 = branch_copies1.movewithdir[f]
887 if f2 in m2:
890 if f2 in m2:
888 mresult.addfile(
891 mresult.addfile(
889 f2,
892 f2,
890 mergestatemod.ACTION_MERGE,
893 mergestatemod.ACTION_MERGE,
891 (f, f2, None, True, pa.node()),
894 (f, f2, None, True, pa.node()),
892 b'remote directory rename, both created',
895 b'remote directory rename, both created',
893 )
896 )
894 else:
897 else:
895 mresult.addfile(
898 mresult.addfile(
896 f2,
899 f2,
897 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
900 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
898 (f, fl1),
901 (f, fl1),
899 b'remote directory rename - move from %s' % f,
902 b'remote directory rename - move from %s' % f,
900 )
903 )
901 elif f in branch_copies1.copy:
904 elif f in branch_copies1.copy:
902 f2 = branch_copies1.copy[f]
905 f2 = branch_copies1.copy[f]
903 mresult.addfile(
906 mresult.addfile(
904 f,
907 f,
905 mergestatemod.ACTION_MERGE,
908 mergestatemod.ACTION_MERGE,
906 (f, f2, f2, False, pa.node()),
909 (f, f2, f2, False, pa.node()),
907 b'local copied/moved from %s' % f2,
910 b'local copied/moved from %s' % f2,
908 )
911 )
909 elif f in ma: # clean, a different, no remote
912 elif f in ma: # clean, a different, no remote
910 if n1 != ma[f]:
913 if n1 != ma[f]:
911 if acceptremote:
914 if acceptremote:
912 mresult.addfile(
915 mresult.addfile(
913 f,
916 f,
914 mergestatemod.ACTION_REMOVE,
917 mergestatemod.ACTION_REMOVE,
915 None,
918 None,
916 b'remote delete',
919 b'remote delete',
917 )
920 )
918 else:
921 else:
919 mresult.addfile(
922 mresult.addfile(
920 f,
923 f,
921 mergestatemod.ACTION_CHANGED_DELETED,
924 mergestatemod.ACTION_CHANGED_DELETED,
922 (f, None, f, False, pa.node()),
925 (f, None, f, False, pa.node()),
923 b'prompt changed/deleted',
926 b'prompt changed/deleted',
924 )
927 )
925 if branchmerge:
928 if branchmerge:
926 mresult.addcommitinfo(
929 mresult.addcommitinfo(
927 f, b'merge-removal-candidate', b'yes'
930 f, b'merge-removal-candidate', b'yes'
928 )
931 )
929 elif n1 == repo.nodeconstants.addednodeid:
932 elif n1 == repo.nodeconstants.addednodeid:
930 # This file was locally added. We should forget it instead of
933 # This file was locally added. We should forget it instead of
931 # deleting it.
934 # deleting it.
932 mresult.addfile(
935 mresult.addfile(
933 f,
936 f,
934 mergestatemod.ACTION_FORGET,
937 mergestatemod.ACTION_FORGET,
935 None,
938 None,
936 b'remote deleted',
939 b'remote deleted',
937 )
940 )
938 else:
941 else:
939 mresult.addfile(
942 mresult.addfile(
940 f,
943 f,
941 mergestatemod.ACTION_REMOVE,
944 mergestatemod.ACTION_REMOVE,
942 None,
945 None,
943 b'other deleted',
946 b'other deleted',
944 )
947 )
945 if branchmerge:
948 if branchmerge:
946 # the file must be absent after merging,
949 # the file must be absent after merging,
947 # howeber the user might make
950 # howeber the user might make
948 # the file reappear using revert and if they does,
951 # the file reappear using revert and if they does,
949 # we force create a new node
952 # we force create a new node
950 mresult.addcommitinfo(
953 mresult.addcommitinfo(
951 f, b'merge-removal-candidate', b'yes'
954 f, b'merge-removal-candidate', b'yes'
952 )
955 )
953
956
954 else: # file not in ancestor, not in remote
957 else: # file not in ancestor, not in remote
955 mresult.addfile(
958 mresult.addfile(
956 f,
959 f,
957 mergestatemod.ACTION_KEEP_NEW,
960 mergestatemod.ACTION_KEEP_NEW,
958 None,
961 None,
959 b'ancestor missing, remote missing',
962 b'ancestor missing, remote missing',
960 )
963 )
961
964
962 elif n2: # file exists only on remote side
965 elif n2: # file exists only on remote side
963 if f in copied1:
966 if f in copied1:
964 pass # we'll deal with it on m1 side
967 pass # we'll deal with it on m1 side
965 elif f in branch_copies2.movewithdir:
968 elif f in branch_copies2.movewithdir:
966 f2 = branch_copies2.movewithdir[f]
969 f2 = branch_copies2.movewithdir[f]
967 if f2 in m1:
970 if f2 in m1:
968 mresult.addfile(
971 mresult.addfile(
969 f2,
972 f2,
970 mergestatemod.ACTION_MERGE,
973 mergestatemod.ACTION_MERGE,
971 (f2, f, None, False, pa.node()),
974 (f2, f, None, False, pa.node()),
972 b'local directory rename, both created',
975 b'local directory rename, both created',
973 )
976 )
974 else:
977 else:
975 mresult.addfile(
978 mresult.addfile(
976 f2,
979 f2,
977 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
980 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
978 (f, fl2),
981 (f, fl2),
979 b'local directory rename - get from %s' % f,
982 b'local directory rename - get from %s' % f,
980 )
983 )
981 elif f in branch_copies2.copy:
984 elif f in branch_copies2.copy:
982 f2 = branch_copies2.copy[f]
985 f2 = branch_copies2.copy[f]
983 msg, args = None, None
986 msg, args = None, None
984 if f2 in m2:
987 if f2 in m2:
985 args = (f2, f, f2, False, pa.node())
988 args = (f2, f, f2, False, pa.node())
986 msg = b'remote copied from %s' % f2
989 msg = b'remote copied from %s' % f2
987 else:
990 else:
988 args = (f2, f, f2, True, pa.node())
991 args = (f2, f, f2, True, pa.node())
989 msg = b'remote moved from %s' % f2
992 msg = b'remote moved from %s' % f2
990 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
993 mresult.addfile(f, mergestatemod.ACTION_MERGE, args, msg)
991 elif f not in ma:
994 elif f not in ma:
992 # local unknown, remote created: the logic is described by the
995 # local unknown, remote created: the logic is described by the
993 # following table:
996 # following table:
994 #
997 #
995 # force branchmerge different | action
998 # force branchmerge different | action
996 # n * * | create
999 # n * * | create
997 # y n * | create
1000 # y n * | create
998 # y y n | create
1001 # y y n | create
999 # y y y | merge
1002 # y y y | merge
1000 #
1003 #
1001 # Checking whether the files are different is expensive, so we
1004 # Checking whether the files are different is expensive, so we
1002 # don't do that when we can avoid it.
1005 # don't do that when we can avoid it.
1003 if not force:
1006 if not force:
1004 mresult.addfile(
1007 mresult.addfile(
1005 f,
1008 f,
1006 mergestatemod.ACTION_CREATED,
1009 mergestatemod.ACTION_CREATED,
1007 (fl2,),
1010 (fl2,),
1008 b'remote created',
1011 b'remote created',
1009 )
1012 )
1010 elif not branchmerge:
1013 elif not branchmerge:
1011 mresult.addfile(
1014 mresult.addfile(
1012 f,
1015 f,
1013 mergestatemod.ACTION_CREATED,
1016 mergestatemod.ACTION_CREATED,
1014 (fl2,),
1017 (fl2,),
1015 b'remote created',
1018 b'remote created',
1016 )
1019 )
1017 else:
1020 else:
1018 mresult.addfile(
1021 mresult.addfile(
1019 f,
1022 f,
1020 mergestatemod.ACTION_CREATED_MERGE,
1023 mergestatemod.ACTION_CREATED_MERGE,
1021 (fl2, pa.node()),
1024 (fl2, pa.node()),
1022 b'remote created, get or merge',
1025 b'remote created, get or merge',
1023 )
1026 )
1024 elif n2 != ma[f]:
1027 elif n2 != ma[f]:
1025 df = None
1028 df = None
1026 for d in branch_copies1.dirmove:
1029 for d in branch_copies1.dirmove:
1027 if f.startswith(d):
1030 if f.startswith(d):
1028 # new file added in a directory that was moved
1031 # new file added in a directory that was moved
1029 df = branch_copies1.dirmove[d] + f[len(d) :]
1032 df = branch_copies1.dirmove[d] + f[len(d) :]
1030 break
1033 break
1031 if df is not None and df in m1:
1034 if df is not None and df in m1:
1032 mresult.addfile(
1035 mresult.addfile(
1033 df,
1036 df,
1034 mergestatemod.ACTION_MERGE,
1037 mergestatemod.ACTION_MERGE,
1035 (df, f, f, False, pa.node()),
1038 (df, f, f, False, pa.node()),
1036 b'local directory rename - respect move '
1039 b'local directory rename - respect move '
1037 b'from %s' % f,
1040 b'from %s' % f,
1038 )
1041 )
1039 elif acceptremote:
1042 elif acceptremote:
1040 mresult.addfile(
1043 mresult.addfile(
1041 f,
1044 f,
1042 mergestatemod.ACTION_CREATED,
1045 mergestatemod.ACTION_CREATED,
1043 (fl2,),
1046 (fl2,),
1044 b'remote recreating',
1047 b'remote recreating',
1045 )
1048 )
1046 else:
1049 else:
1047 mresult.addfile(
1050 mresult.addfile(
1048 f,
1051 f,
1049 mergestatemod.ACTION_DELETED_CHANGED,
1052 mergestatemod.ACTION_DELETED_CHANGED,
1050 (None, f, f, False, pa.node()),
1053 (None, f, f, False, pa.node()),
1051 b'prompt deleted/changed',
1054 b'prompt deleted/changed',
1052 )
1055 )
1053 if branchmerge:
1056 if branchmerge:
1054 mresult.addcommitinfo(
1057 mresult.addcommitinfo(
1055 f, b'merge-removal-candidate', b'yes'
1058 f, b'merge-removal-candidate', b'yes'
1056 )
1059 )
1057 else:
1060 else:
1058 mresult.addfile(
1061 mresult.addfile(
1059 f,
1062 f,
1060 mergestatemod.ACTION_KEEP_ABSENT,
1063 mergestatemod.ACTION_KEEP_ABSENT,
1061 None,
1064 None,
1062 b'local not present, remote unchanged',
1065 b'local not present, remote unchanged',
1063 )
1066 )
1064 if branchmerge:
1067 if branchmerge:
1065 # the file must be absent after merging
1068 # the file must be absent after merging
1066 # however the user might make
1069 # however the user might make
1067 # the file reappear using revert and if they does,
1070 # the file reappear using revert and if they does,
1068 # we force create a new node
1071 # we force create a new node
1069 mresult.addcommitinfo(f, b'merge-removal-candidate', b'yes')
1072 mresult.addcommitinfo(f, b'merge-removal-candidate', b'yes')
1070
1073
1071 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1074 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1072 # If we are merging, look for path conflicts.
1075 # If we are merging, look for path conflicts.
1073 checkpathconflicts(repo, wctx, p2, mresult)
1076 checkpathconflicts(repo, wctx, p2, mresult)
1074
1077
1075 narrowmatch = repo.narrowmatch()
1078 narrowmatch = repo.narrowmatch()
1076 if not narrowmatch.always():
1079 if not narrowmatch.always():
1077 # Updates "actions" in place
1080 # Updates "actions" in place
1078 _filternarrowactions(narrowmatch, branchmerge, mresult)
1081 _filternarrowactions(narrowmatch, branchmerge, mresult)
1079
1082
1080 renamedelete = branch_copies1.renamedelete
1083 renamedelete = branch_copies1.renamedelete
1081 renamedelete.update(branch_copies2.renamedelete)
1084 renamedelete.update(branch_copies2.renamedelete)
1082
1085
1083 mresult.updatevalues(diverge, renamedelete)
1086 mresult.updatevalues(diverge, renamedelete)
1084 return mresult
1087 return mresult
1085
1088
1086
1089
1087 def _resolvetrivial(repo, wctx, mctx, ancestor, mresult):
1090 def _resolvetrivial(repo, wctx, mctx, ancestor, mresult):
1088 """Resolves false conflicts where the nodeid changed but the content
1091 """Resolves false conflicts where the nodeid changed but the content
1089 remained the same."""
1092 remained the same."""
1090 # We force a copy of actions.items() because we're going to mutate
1093 # We force a copy of actions.items() because we're going to mutate
1091 # actions as we resolve trivial conflicts.
1094 # actions as we resolve trivial conflicts.
1092 for f in list(mresult.files((mergestatemod.ACTION_CHANGED_DELETED,))):
1095 for f in list(mresult.files((mergestatemod.ACTION_CHANGED_DELETED,))):
1093 if f in ancestor and not wctx[f].cmp(ancestor[f]):
1096 if f in ancestor and not wctx[f].cmp(ancestor[f]):
1094 # local did change but ended up with same content
1097 # local did change but ended up with same content
1095 mresult.addfile(
1098 mresult.addfile(
1096 f, mergestatemod.ACTION_REMOVE, None, b'prompt same'
1099 f, mergestatemod.ACTION_REMOVE, None, b'prompt same'
1097 )
1100 )
1098
1101
1099 for f in list(mresult.files((mergestatemod.ACTION_DELETED_CHANGED,))):
1102 for f in list(mresult.files((mergestatemod.ACTION_DELETED_CHANGED,))):
1100 if f in ancestor and not mctx[f].cmp(ancestor[f]):
1103 if f in ancestor and not mctx[f].cmp(ancestor[f]):
1101 # remote did change but ended up with same content
1104 # remote did change but ended up with same content
1102 mresult.removefile(f) # don't get = keep local deleted
1105 mresult.removefile(f) # don't get = keep local deleted
1103
1106
1104
1107
1105 def calculateupdates(
1108 def calculateupdates(
1106 repo,
1109 repo,
1107 wctx,
1110 wctx,
1108 mctx,
1111 mctx,
1109 ancestors,
1112 ancestors,
1110 branchmerge,
1113 branchmerge,
1111 force,
1114 force,
1112 acceptremote,
1115 acceptremote,
1113 followcopies,
1116 followcopies,
1114 matcher=None,
1117 matcher=None,
1115 mergeforce=False,
1118 mergeforce=False,
1116 ):
1119 ):
1117 """
1120 """
1118 Calculate the actions needed to merge mctx into wctx using ancestors
1121 Calculate the actions needed to merge mctx into wctx using ancestors
1119
1122
1120 Uses manifestmerge() to merge manifest and get list of actions required to
1123 Uses manifestmerge() to merge manifest and get list of actions required to
1121 perform for merging two manifests. If there are multiple ancestors, uses bid
1124 perform for merging two manifests. If there are multiple ancestors, uses bid
1122 merge if enabled.
1125 merge if enabled.
1123
1126
1124 Also filters out actions which are unrequired if repository is sparse.
1127 Also filters out actions which are unrequired if repository is sparse.
1125
1128
1126 Returns mergeresult object same as manifestmerge().
1129 Returns mergeresult object same as manifestmerge().
1127 """
1130 """
1128 # Avoid cycle.
1131 # Avoid cycle.
1129 from . import sparse
1132 from . import sparse
1130
1133
1131 mresult = None
1134 mresult = None
1132 if len(ancestors) == 1: # default
1135 if len(ancestors) == 1: # default
1133 mresult = manifestmerge(
1136 mresult = manifestmerge(
1134 repo,
1137 repo,
1135 wctx,
1138 wctx,
1136 mctx,
1139 mctx,
1137 ancestors[0],
1140 ancestors[0],
1138 branchmerge,
1141 branchmerge,
1139 force,
1142 force,
1140 matcher,
1143 matcher,
1141 acceptremote,
1144 acceptremote,
1142 followcopies,
1145 followcopies,
1143 )
1146 )
1144 _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce)
1147 _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce)
1145 if repo.ui.configbool(b'devel', b'debug.abort-update'):
1148 if repo.ui.configbool(b'devel', b'debug.abort-update'):
1146 exit(1)
1149 exit(1)
1147
1150
1148 else: # only when merge.preferancestor=* - the default
1151 else: # only when merge.preferancestor=* - the default
1149 repo.ui.note(
1152 repo.ui.note(
1150 _(b"note: merging %s and %s using bids from ancestors %s\n")
1153 _(b"note: merging %s and %s using bids from ancestors %s\n")
1151 % (
1154 % (
1152 wctx,
1155 wctx,
1153 mctx,
1156 mctx,
1154 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1157 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1155 )
1158 )
1156 )
1159 )
1157
1160
1158 # mapping filename to bids (action method to list af actions)
1161 # mapping filename to bids (action method to list af actions)
1159 # {FILENAME1 : BID1, FILENAME2 : BID2}
1162 # {FILENAME1 : BID1, FILENAME2 : BID2}
1160 # BID is another dictionary which contains
1163 # BID is another dictionary which contains
1161 # mapping of following form:
1164 # mapping of following form:
1162 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1165 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1163 fbids = {}
1166 fbids = {}
1164 mresult = mergeresult()
1167 mresult = mergeresult()
1165 diverge, renamedelete = None, None
1168 diverge, renamedelete = None, None
1166 for ancestor in ancestors:
1169 for ancestor in ancestors:
1167 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1170 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1168 mresult1 = manifestmerge(
1171 mresult1 = manifestmerge(
1169 repo,
1172 repo,
1170 wctx,
1173 wctx,
1171 mctx,
1174 mctx,
1172 ancestor,
1175 ancestor,
1173 branchmerge,
1176 branchmerge,
1174 force,
1177 force,
1175 matcher,
1178 matcher,
1176 acceptremote,
1179 acceptremote,
1177 followcopies,
1180 followcopies,
1178 forcefulldiff=True,
1181 forcefulldiff=True,
1179 )
1182 )
1180 _checkunknownfiles(repo, wctx, mctx, force, mresult1, mergeforce)
1183 _checkunknownfiles(repo, wctx, mctx, force, mresult1, mergeforce)
1181
1184
1182 # Track the shortest set of warning on the theory that bid
1185 # Track the shortest set of warning on the theory that bid
1183 # merge will correctly incorporate more information
1186 # merge will correctly incorporate more information
1184 if diverge is None or len(mresult1.diverge) < len(diverge):
1187 if diverge is None or len(mresult1.diverge) < len(diverge):
1185 diverge = mresult1.diverge
1188 diverge = mresult1.diverge
1186 if renamedelete is None or len(renamedelete) < len(
1189 if renamedelete is None or len(renamedelete) < len(
1187 mresult1.renamedelete
1190 mresult1.renamedelete
1188 ):
1191 ):
1189 renamedelete = mresult1.renamedelete
1192 renamedelete = mresult1.renamedelete
1190
1193
1191 # blindly update final mergeresult commitinfo with what we get
1194 # blindly update final mergeresult commitinfo with what we get
1192 # from mergeresult object for each ancestor
1195 # from mergeresult object for each ancestor
1193 # TODO: some commitinfo depends on what bid merge choose and hence
1196 # TODO: some commitinfo depends on what bid merge choose and hence
1194 # we will need to make commitinfo also depend on bid merge logic
1197 # we will need to make commitinfo also depend on bid merge logic
1195 mresult._commitinfo.update(mresult1._commitinfo)
1198 mresult._commitinfo.update(mresult1._commitinfo)
1196
1199
1197 for f, a in mresult1.filemap(sort=True):
1200 for f, a in mresult1.filemap(sort=True):
1198 m, args, msg = a
1201 m, args, msg = a
1199 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m.__bytes__()))
1202 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m.__bytes__()))
1200 if f in fbids:
1203 if f in fbids:
1201 d = fbids[f]
1204 d = fbids[f]
1202 if m in d:
1205 if m in d:
1203 d[m].append(a)
1206 d[m].append(a)
1204 else:
1207 else:
1205 d[m] = [a]
1208 d[m] = [a]
1206 else:
1209 else:
1207 fbids[f] = {m: [a]}
1210 fbids[f] = {m: [a]}
1208
1211
1209 # Call for bids
1212 # Call for bids
1210 # Pick the best bid for each file
1213 # Pick the best bid for each file
1211 repo.ui.note(
1214 repo.ui.note(
1212 _(b'\nauction for merging merge bids (%d ancestors)\n')
1215 _(b'\nauction for merging merge bids (%d ancestors)\n')
1213 % len(ancestors)
1216 % len(ancestors)
1214 )
1217 )
1215 for f, bids in sorted(fbids.items()):
1218 for f, bids in sorted(fbids.items()):
1216 if repo.ui.debugflag:
1219 if repo.ui.debugflag:
1217 repo.ui.debug(b" list of bids for %s:\n" % f)
1220 repo.ui.debug(b" list of bids for %s:\n" % f)
1218 for m, l in sorted(bids.items()):
1221 for m, l in sorted(bids.items()):
1219 for _f, args, msg in l:
1222 for _f, args, msg in l:
1220 repo.ui.debug(b' %s -> %s\n' % (msg, m.__bytes__()))
1223 repo.ui.debug(b' %s -> %s\n' % (msg, m.__bytes__()))
1221 # bids is a mapping from action method to list af actions
1224 # bids is a mapping from action method to list af actions
1222 # Consensus?
1225 # Consensus?
1223 if len(bids) == 1: # all bids are the same kind of method
1226 if len(bids) == 1: # all bids are the same kind of method
1224 m, l = list(bids.items())[0]
1227 m, l = list(bids.items())[0]
1225 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1228 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1226 repo.ui.note(
1229 repo.ui.note(
1227 _(b" %s: consensus for %s\n") % (f, m.__bytes__())
1230 _(b" %s: consensus for %s\n") % (f, m.__bytes__())
1228 )
1231 )
1229 mresult.addfile(f, *l[0])
1232 mresult.addfile(f, *l[0])
1230 continue
1233 continue
1231 # If keep is an option, just do it.
1234 # If keep is an option, just do it.
1232 if mergestatemod.ACTION_KEEP in bids:
1235 if mergestatemod.ACTION_KEEP in bids:
1233 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1236 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1234 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1237 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1235 continue
1238 continue
1236 # If keep absent is an option, just do that
1239 # If keep absent is an option, just do that
1237 if mergestatemod.ACTION_KEEP_ABSENT in bids:
1240 if mergestatemod.ACTION_KEEP_ABSENT in bids:
1238 repo.ui.note(_(b" %s: picking 'keep absent' action\n") % f)
1241 repo.ui.note(_(b" %s: picking 'keep absent' action\n") % f)
1239 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_ABSENT][0])
1242 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_ABSENT][0])
1240 continue
1243 continue
1241 # ACTION_KEEP_NEW and ACTION_CHANGED_DELETED are conflicting actions
1244 # ACTION_KEEP_NEW and ACTION_CHANGED_DELETED are conflicting actions
1242 # as one say that file is new while other says that file was present
1245 # as one say that file is new while other says that file was present
1243 # earlier too and has a change delete conflict
1246 # earlier too and has a change delete conflict
1244 # Let's fall back to conflicting ACTION_CHANGED_DELETED and let user
1247 # Let's fall back to conflicting ACTION_CHANGED_DELETED and let user
1245 # do the right thing
1248 # do the right thing
1246 if (
1249 if (
1247 mergestatemod.ACTION_CHANGED_DELETED in bids
1250 mergestatemod.ACTION_CHANGED_DELETED in bids
1248 and mergestatemod.ACTION_KEEP_NEW in bids
1251 and mergestatemod.ACTION_KEEP_NEW in bids
1249 ):
1252 ):
1250 repo.ui.note(_(b" %s: picking 'changed/deleted' action\n") % f)
1253 repo.ui.note(_(b" %s: picking 'changed/deleted' action\n") % f)
1251 mresult.addfile(
1254 mresult.addfile(
1252 f, *bids[mergestatemod.ACTION_CHANGED_DELETED][0]
1255 f, *bids[mergestatemod.ACTION_CHANGED_DELETED][0]
1253 )
1256 )
1254 continue
1257 continue
1255 # If keep new is an option, let's just do that
1258 # If keep new is an option, let's just do that
1256 if mergestatemod.ACTION_KEEP_NEW in bids:
1259 if mergestatemod.ACTION_KEEP_NEW in bids:
1257 repo.ui.note(_(b" %s: picking 'keep new' action\n") % f)
1260 repo.ui.note(_(b" %s: picking 'keep new' action\n") % f)
1258 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_NEW][0])
1261 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP_NEW][0])
1259 continue
1262 continue
1260 # ACTION_GET and ACTION_DELETE_CHANGED are conflicting actions as
1263 # ACTION_GET and ACTION_DELETE_CHANGED are conflicting actions as
1261 # one action states the file is newer/created on remote side and
1264 # one action states the file is newer/created on remote side and
1262 # other states that file is deleted locally and changed on remote
1265 # other states that file is deleted locally and changed on remote
1263 # side. Let's fallback and rely on a conflicting action to let user
1266 # side. Let's fallback and rely on a conflicting action to let user
1264 # do the right thing
1267 # do the right thing
1265 if (
1268 if (
1266 mergestatemod.ACTION_DELETED_CHANGED in bids
1269 mergestatemod.ACTION_DELETED_CHANGED in bids
1267 and mergestatemod.ACTION_GET in bids
1270 and mergestatemod.ACTION_GET in bids
1268 ):
1271 ):
1269 repo.ui.note(_(b" %s: picking 'delete/changed' action\n") % f)
1272 repo.ui.note(_(b" %s: picking 'delete/changed' action\n") % f)
1270 mresult.addfile(
1273 mresult.addfile(
1271 f, *bids[mergestatemod.ACTION_DELETED_CHANGED][0]
1274 f, *bids[mergestatemod.ACTION_DELETED_CHANGED][0]
1272 )
1275 )
1273 continue
1276 continue
1274 # If there are gets and they all agree [how could they not?], do it.
1277 # If there are gets and they all agree [how could they not?], do it.
1275 if mergestatemod.ACTION_GET in bids:
1278 if mergestatemod.ACTION_GET in bids:
1276 ga0 = bids[mergestatemod.ACTION_GET][0]
1279 ga0 = bids[mergestatemod.ACTION_GET][0]
1277 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1280 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1278 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1281 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1279 mresult.addfile(f, *ga0)
1282 mresult.addfile(f, *ga0)
1280 continue
1283 continue
1281 # TODO: Consider other simple actions such as mode changes
1284 # TODO: Consider other simple actions such as mode changes
1282 # Handle inefficient democrazy.
1285 # Handle inefficient democrazy.
1283 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1286 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1284 for m, l in sorted(bids.items()):
1287 for m, l in sorted(bids.items()):
1285 for _f, args, msg in l:
1288 for _f, args, msg in l:
1286 repo.ui.note(b' %s -> %s\n' % (msg, m.__bytes__()))
1289 repo.ui.note(b' %s -> %s\n' % (msg, m.__bytes__()))
1287 # Pick random action. TODO: Instead, prompt user when resolving
1290 # Pick random action. TODO: Instead, prompt user when resolving
1288 m, l = list(bids.items())[0]
1291 m, l = list(bids.items())[0]
1289 repo.ui.warn(
1292 repo.ui.warn(
1290 _(b' %s: ambiguous merge - picked %s action\n')
1293 _(b' %s: ambiguous merge - picked %s action\n')
1291 % (f, m.__bytes__())
1294 % (f, m.__bytes__())
1292 )
1295 )
1293 mresult.addfile(f, *l[0])
1296 mresult.addfile(f, *l[0])
1294 continue
1297 continue
1295 repo.ui.note(_(b'end of auction\n\n'))
1298 repo.ui.note(_(b'end of auction\n\n'))
1296 mresult.updatevalues(diverge, renamedelete)
1299 mresult.updatevalues(diverge, renamedelete)
1297
1300
1298 if wctx.rev() is None:
1301 if wctx.rev() is None:
1299 _forgetremoved(wctx, mctx, branchmerge, mresult)
1302 _forgetremoved(wctx, mctx, branchmerge, mresult)
1300
1303
1301 sparse.filterupdatesactions(repo, wctx, mctx, branchmerge, mresult)
1304 sparse.filterupdatesactions(repo, wctx, mctx, branchmerge, mresult)
1302 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult)
1305 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult)
1303
1306
1304 return mresult
1307 return mresult
1305
1308
1306
1309
1307 def _getcwd():
1310 def _getcwd():
1308 try:
1311 try:
1309 return encoding.getcwd()
1312 return encoding.getcwd()
1310 except FileNotFoundError:
1313 except FileNotFoundError:
1311 return None
1314 return None
1312
1315
1313
1316
1314 def batchremove(repo, wctx, actions):
1317 def batchremove(repo, wctx, actions):
1315 """apply removes to the working directory
1318 """apply removes to the working directory
1316
1319
1317 yields tuples for progress updates
1320 yields tuples for progress updates
1318 """
1321 """
1319 verbose = repo.ui.verbose
1322 verbose = repo.ui.verbose
1320 cwd = _getcwd()
1323 cwd = _getcwd()
1321 i = 0
1324 i = 0
1322 for f, args, msg in actions:
1325 for f, args, msg in actions:
1323 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1326 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1324 if verbose:
1327 if verbose:
1325 repo.ui.note(_(b"removing %s\n") % f)
1328 repo.ui.note(_(b"removing %s\n") % f)
1326 wctx[f].audit()
1329 wctx[f].audit()
1327 try:
1330 try:
1328 wctx[f].remove(ignoremissing=True)
1331 wctx[f].remove(ignoremissing=True)
1329 except OSError as inst:
1332 except OSError as inst:
1330 repo.ui.warn(
1333 repo.ui.warn(
1331 _(b"update failed to remove %s: %s!\n")
1334 _(b"update failed to remove %s: %s!\n")
1332 % (f, stringutil.forcebytestr(inst.strerror))
1335 % (f, stringutil.forcebytestr(inst.strerror))
1333 )
1336 )
1334 if i == 100:
1337 if i == 100:
1335 yield i, f
1338 yield i, f
1336 i = 0
1339 i = 0
1337 i += 1
1340 i += 1
1338 if i > 0:
1341 if i > 0:
1339 yield i, f
1342 yield i, f
1340
1343
1341 if cwd and not _getcwd():
1344 if cwd and not _getcwd():
1342 # cwd was removed in the course of removing files; print a helpful
1345 # cwd was removed in the course of removing files; print a helpful
1343 # warning.
1346 # warning.
1344 repo.ui.warn(
1347 repo.ui.warn(
1345 _(
1348 _(
1346 b"current directory was removed\n"
1349 b"current directory was removed\n"
1347 b"(consider changing to repo root: %s)\n"
1350 b"(consider changing to repo root: %s)\n"
1348 )
1351 )
1349 % repo.root
1352 % repo.root
1350 )
1353 )
1351
1354
1352
1355
1353 def batchget(repo, mctx, wctx, wantfiledata, actions):
1356 def batchget(repo, mctx, wctx, wantfiledata, actions):
1354 """apply gets to the working directory
1357 """apply gets to the working directory
1355
1358
1356 mctx is the context to get from
1359 mctx is the context to get from
1357
1360
1358 Yields arbitrarily many (False, tuple) for progress updates, followed by
1361 Yields arbitrarily many (False, tuple) for progress updates, followed by
1359 exactly one (True, filedata). When wantfiledata is false, filedata is an
1362 exactly one (True, filedata). When wantfiledata is false, filedata is an
1360 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1363 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1361 mtime) of the file f written for each action.
1364 mtime) of the file f written for each action.
1362 """
1365 """
1363 filedata = {}
1366 filedata = {}
1364 verbose = repo.ui.verbose
1367 verbose = repo.ui.verbose
1365 fctx = mctx.filectx
1368 fctx = mctx.filectx
1366 ui = repo.ui
1369 ui = repo.ui
1367 i = 0
1370 i = 0
1368 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1371 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1369 for f, (flags, backup), msg in actions:
1372 for f, (flags, backup), msg in actions:
1370 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1373 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1371 if verbose:
1374 if verbose:
1372 repo.ui.note(_(b"getting %s\n") % f)
1375 repo.ui.note(_(b"getting %s\n") % f)
1373
1376
1374 if backup:
1377 if backup:
1375 # If a file or directory exists with the same name, back that
1378 # If a file or directory exists with the same name, back that
1376 # up. Otherwise, look to see if there is a file that conflicts
1379 # up. Otherwise, look to see if there is a file that conflicts
1377 # with a directory this file is in, and if so, back that up.
1380 # with a directory this file is in, and if so, back that up.
1378 conflicting = f
1381 conflicting = f
1379 if not repo.wvfs.lexists(f):
1382 if not repo.wvfs.lexists(f):
1380 for p in pathutil.finddirs(f):
1383 for p in pathutil.finddirs(f):
1381 if repo.wvfs.isfileorlink(p):
1384 if repo.wvfs.isfileorlink(p):
1382 conflicting = p
1385 conflicting = p
1383 break
1386 break
1384 if repo.wvfs.lexists(conflicting):
1387 if repo.wvfs.lexists(conflicting):
1385 orig = scmutil.backuppath(ui, repo, conflicting)
1388 orig = scmutil.backuppath(ui, repo, conflicting)
1386 util.rename(repo.wjoin(conflicting), orig)
1389 util.rename(repo.wjoin(conflicting), orig)
1387 wfctx = wctx[f]
1390 wfctx = wctx[f]
1388 wfctx.clearunknown()
1391 wfctx.clearunknown()
1389 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1392 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1390 size = wfctx.write(
1393 size = wfctx.write(
1391 fctx(f).data(),
1394 fctx(f).data(),
1392 flags,
1395 flags,
1393 backgroundclose=True,
1396 backgroundclose=True,
1394 atomictemp=atomictemp,
1397 atomictemp=atomictemp,
1395 )
1398 )
1396 if wantfiledata:
1399 if wantfiledata:
1397 # XXX note that there is a race window between the time we
1400 # XXX note that there is a race window between the time we
1398 # write the clean data into the file and we stats it. So another
1401 # write the clean data into the file and we stats it. So another
1399 # writing process meddling with the file content right after we
1402 # writing process meddling with the file content right after we
1400 # wrote it could cause bad stat data to be gathered.
1403 # wrote it could cause bad stat data to be gathered.
1401 #
1404 #
1402 # They are 2 data we gather here
1405 # They are 2 data we gather here
1403 # - the mode:
1406 # - the mode:
1404 # That we actually just wrote, we should not need to read
1407 # That we actually just wrote, we should not need to read
1405 # it from disk, (except not all mode might have survived
1408 # it from disk, (except not all mode might have survived
1406 # the disk round-trip, which is another issue: we should
1409 # the disk round-trip, which is another issue: we should
1407 # not depends on this)
1410 # not depends on this)
1408 # - the mtime,
1411 # - the mtime,
1409 # On system that support nanosecond precision, the mtime
1412 # On system that support nanosecond precision, the mtime
1410 # could be accurate enough to tell the two writes appart.
1413 # could be accurate enough to tell the two writes appart.
1411 # However gathering it in a racy way make the mtime we
1414 # However gathering it in a racy way make the mtime we
1412 # gather "unreliable".
1415 # gather "unreliable".
1413 #
1416 #
1414 # (note: we get the size from the data we write, which is sane)
1417 # (note: we get the size from the data we write, which is sane)
1415 #
1418 #
1416 # So in theory the data returned here are fully racy, but in
1419 # So in theory the data returned here are fully racy, but in
1417 # practice "it works mostly fine".
1420 # practice "it works mostly fine".
1418 #
1421 #
1419 # Do not be surprised if you end up reading this while looking
1422 # Do not be surprised if you end up reading this while looking
1420 # for the causes of some buggy status. Feel free to improve
1423 # for the causes of some buggy status. Feel free to improve
1421 # this in the future, but we cannot simply stop gathering
1424 # this in the future, but we cannot simply stop gathering
1422 # information. Otherwise `hg status` call made after a large `hg
1425 # information. Otherwise `hg status` call made after a large `hg
1423 # update` runs would have to redo a similar amount of work to
1426 # update` runs would have to redo a similar amount of work to
1424 # restore and compare all files content.
1427 # restore and compare all files content.
1425 s = wfctx.lstat()
1428 s = wfctx.lstat()
1426 mode = s.st_mode
1429 mode = s.st_mode
1427 mtime = timestamp.mtime_of(s)
1430 mtime = timestamp.mtime_of(s)
1428 # for dirstate.update_file's parentfiledata argument:
1431 # for dirstate.update_file's parentfiledata argument:
1429 filedata[f] = (mode, size, mtime)
1432 filedata[f] = (mode, size, mtime)
1430 if i == 100:
1433 if i == 100:
1431 yield False, (i, f)
1434 yield False, (i, f)
1432 i = 0
1435 i = 0
1433 i += 1
1436 i += 1
1434 if i > 0:
1437 if i > 0:
1435 yield False, (i, f)
1438 yield False, (i, f)
1436 yield True, filedata
1439 yield True, filedata
1437
1440
1438
1441
1439 def _prefetchfiles(repo, ctx, mresult):
1442 def _prefetchfiles(repo, ctx, mresult):
1440 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1443 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1441 of merge actions. ``ctx`` is the context being merged in."""
1444 of merge actions. ``ctx`` is the context being merged in."""
1442
1445
1443 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1446 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1444 # don't touch the context to be merged in. 'cd' is skipped, because
1447 # don't touch the context to be merged in. 'cd' is skipped, because
1445 # changed/deleted never resolves to something from the remote side.
1448 # changed/deleted never resolves to something from the remote side.
1446 files = mresult.files(
1449 files = mresult.files(
1447 [
1450 [
1448 mergestatemod.ACTION_GET,
1451 mergestatemod.ACTION_GET,
1449 mergestatemod.ACTION_DELETED_CHANGED,
1452 mergestatemod.ACTION_DELETED_CHANGED,
1450 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1453 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1451 mergestatemod.ACTION_MERGE,
1454 mergestatemod.ACTION_MERGE,
1452 ]
1455 ]
1453 )
1456 )
1454
1457
1455 prefetch = scmutil.prefetchfiles
1458 prefetch = scmutil.prefetchfiles
1456 matchfiles = scmutil.matchfiles
1459 matchfiles = scmutil.matchfiles
1457 prefetch(
1460 prefetch(
1458 repo,
1461 repo,
1459 [
1462 [
1460 (
1463 (
1461 ctx.rev(),
1464 ctx.rev(),
1462 matchfiles(repo, files),
1465 matchfiles(repo, files),
1463 )
1466 )
1464 ],
1467 ],
1465 )
1468 )
1466
1469
1467
1470
1468 @attr.s(frozen=True)
1471 @attr.s(frozen=True)
1469 class updateresult:
1472 class updateresult:
1470 updatedcount = attr.ib()
1473 updatedcount = attr.ib()
1471 mergedcount = attr.ib()
1474 mergedcount = attr.ib()
1472 removedcount = attr.ib()
1475 removedcount = attr.ib()
1473 unresolvedcount = attr.ib()
1476 unresolvedcount = attr.ib()
1474
1477
1475 def isempty(self):
1478 def isempty(self):
1476 return not (
1479 return not (
1477 self.updatedcount
1480 self.updatedcount
1478 or self.mergedcount
1481 or self.mergedcount
1479 or self.removedcount
1482 or self.removedcount
1480 or self.unresolvedcount
1483 or self.unresolvedcount
1481 )
1484 )
1482
1485
1483
1486
1484 def applyupdates(
1487 def applyupdates(
1485 repo,
1488 repo,
1486 mresult,
1489 mresult,
1487 wctx,
1490 wctx,
1488 mctx,
1491 mctx,
1489 overwrite,
1492 overwrite,
1490 wantfiledata,
1493 wantfiledata,
1491 labels=None,
1494 labels=None,
1492 ):
1495 ):
1493 """apply the merge action list to the working directory
1496 """apply the merge action list to the working directory
1494
1497
1495 mresult is a mergeresult object representing result of the merge
1498 mresult is a mergeresult object representing result of the merge
1496 wctx is the working copy context
1499 wctx is the working copy context
1497 mctx is the context to be merged into the working copy
1500 mctx is the context to be merged into the working copy
1498
1501
1499 Return a tuple of (counts, filedata), where counts is a tuple
1502 Return a tuple of (counts, filedata), where counts is a tuple
1500 (updated, merged, removed, unresolved) that describes how many
1503 (updated, merged, removed, unresolved) that describes how many
1501 files were affected by the update, and filedata is as described in
1504 files were affected by the update, and filedata is as described in
1502 batchget.
1505 batchget.
1503 """
1506 """
1504
1507
1505 _prefetchfiles(repo, mctx, mresult)
1508 _prefetchfiles(repo, mctx, mresult)
1506
1509
1507 updated, merged, removed = 0, 0, 0
1510 updated, merged, removed = 0, 0, 0
1508 ms = wctx.mergestate(clean=True)
1511 ms = wctx.mergestate(clean=True)
1509 ms.start(wctx.p1().node(), mctx.node(), labels)
1512 ms.start(wctx.p1().node(), mctx.node(), labels)
1510
1513
1511 for f, op in mresult.commitinfo.items():
1514 for f, op in mresult.commitinfo.items():
1512 # the other side of filenode was choosen while merging, store this in
1515 # the other side of filenode was choosen while merging, store this in
1513 # mergestate so that it can be reused on commit
1516 # mergestate so that it can be reused on commit
1514 ms.addcommitinfo(f, op)
1517 ms.addcommitinfo(f, op)
1515
1518
1516 num_no_op = mresult.len(mergestatemod.MergeAction.NO_OP_ACTIONS)
1519 num_no_op = mresult.len(mergestatemod.MergeAction.NO_OP_ACTIONS)
1517 numupdates = mresult.len() - num_no_op
1520 numupdates = mresult.len() - num_no_op
1518 progress = repo.ui.makeprogress(
1521 progress = repo.ui.makeprogress(
1519 _(b'updating'), unit=_(b'files'), total=numupdates
1522 _(b'updating'), unit=_(b'files'), total=numupdates
1520 )
1523 )
1521
1524
1522 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_REMOVE]:
1525 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_REMOVE]:
1523 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1526 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1524
1527
1525 # record path conflicts
1528 # record path conflicts
1526 for f, args, msg in mresult.getactions(
1529 for f, args, msg in mresult.getactions(
1527 [mergestatemod.ACTION_PATH_CONFLICT], sort=True
1530 [mergestatemod.ACTION_PATH_CONFLICT], sort=True
1528 ):
1531 ):
1529 f1, fo = args
1532 f1, fo = args
1530 s = repo.ui.status
1533 s = repo.ui.status
1531 s(
1534 s(
1532 _(
1535 _(
1533 b"%s: path conflict - a file or link has the same name as a "
1536 b"%s: path conflict - a file or link has the same name as a "
1534 b"directory\n"
1537 b"directory\n"
1535 )
1538 )
1536 % f
1539 % f
1537 )
1540 )
1538 if fo == b'l':
1541 if fo == b'l':
1539 s(_(b"the local file has been renamed to %s\n") % f1)
1542 s(_(b"the local file has been renamed to %s\n") % f1)
1540 else:
1543 else:
1541 s(_(b"the remote file has been renamed to %s\n") % f1)
1544 s(_(b"the remote file has been renamed to %s\n") % f1)
1542 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1545 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1543 ms.addpathconflict(f, f1, fo)
1546 ms.addpathconflict(f, f1, fo)
1544 progress.increment(item=f)
1547 progress.increment(item=f)
1545
1548
1546 # When merging in-memory, we can't support worker processes, so set the
1549 # When merging in-memory, we can't support worker processes, so set the
1547 # per-item cost at 0 in that case.
1550 # per-item cost at 0 in that case.
1548 cost = 0 if wctx.isinmemory() else 0.001
1551 cost = 0 if wctx.isinmemory() else 0.001
1549
1552
1550 # remove in parallel (must come before resolving path conflicts and getting)
1553 # remove in parallel (must come before resolving path conflicts and getting)
1551 prog = worker.worker(
1554 prog = worker.worker(
1552 repo.ui,
1555 repo.ui,
1553 cost,
1556 cost,
1554 batchremove,
1557 batchremove,
1555 (repo, wctx),
1558 (repo, wctx),
1556 list(mresult.getactions([mergestatemod.ACTION_REMOVE], sort=True)),
1559 list(mresult.getactions([mergestatemod.ACTION_REMOVE], sort=True)),
1557 )
1560 )
1558 for i, item in prog:
1561 for i, item in prog:
1559 progress.increment(step=i, item=item)
1562 progress.increment(step=i, item=item)
1560 removed = mresult.len((mergestatemod.ACTION_REMOVE,))
1563 removed = mresult.len((mergestatemod.ACTION_REMOVE,))
1561
1564
1562 # resolve path conflicts (must come before getting)
1565 # resolve path conflicts (must come before getting)
1563 for f, args, msg in mresult.getactions(
1566 for f, args, msg in mresult.getactions(
1564 [mergestatemod.ACTION_PATH_CONFLICT_RESOLVE], sort=True
1567 [mergestatemod.ACTION_PATH_CONFLICT_RESOLVE], sort=True
1565 ):
1568 ):
1566 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1569 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1567 (f0, origf0) = args
1570 (f0, origf0) = args
1568 if wctx[f0].lexists():
1571 if wctx[f0].lexists():
1569 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1572 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1570 wctx[f].audit()
1573 wctx[f].audit()
1571 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1574 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1572 wctx[f0].remove()
1575 wctx[f0].remove()
1573 progress.increment(item=f)
1576 progress.increment(item=f)
1574
1577
1575 # get in parallel.
1578 # get in parallel.
1576 threadsafe = repo.ui.configbool(
1579 threadsafe = repo.ui.configbool(
1577 b'experimental', b'worker.wdir-get-thread-safe'
1580 b'experimental', b'worker.wdir-get-thread-safe'
1578 )
1581 )
1579 prog = worker.worker(
1582 prog = worker.worker(
1580 repo.ui,
1583 repo.ui,
1581 cost,
1584 cost,
1582 batchget,
1585 batchget,
1583 (repo, mctx, wctx, wantfiledata),
1586 (repo, mctx, wctx, wantfiledata),
1584 list(mresult.getactions([mergestatemod.ACTION_GET], sort=True)),
1587 list(mresult.getactions([mergestatemod.ACTION_GET], sort=True)),
1585 threadsafe=threadsafe,
1588 threadsafe=threadsafe,
1586 hasretval=True,
1589 hasretval=True,
1587 )
1590 )
1588 getfiledata = {}
1591 getfiledata = {}
1589 for final, res in prog:
1592 for final, res in prog:
1590 if final:
1593 if final:
1591 getfiledata = res
1594 getfiledata = res
1592 else:
1595 else:
1593 i, item = res
1596 i, item = res
1594 progress.increment(step=i, item=item)
1597 progress.increment(step=i, item=item)
1595
1598
1596 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_GET]:
1599 if b'.hgsubstate' in mresult._actionmapping[mergestatemod.ACTION_GET]:
1597 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1600 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1598
1601
1599 # forget (manifest only, just log it) (must come first)
1602 # forget (manifest only, just log it) (must come first)
1600 for f, args, msg in mresult.getactions(
1603 for f, args, msg in mresult.getactions(
1601 (mergestatemod.ACTION_FORGET,), sort=True
1604 (mergestatemod.ACTION_FORGET,), sort=True
1602 ):
1605 ):
1603 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1606 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1604 progress.increment(item=f)
1607 progress.increment(item=f)
1605
1608
1606 # re-add (manifest only, just log it)
1609 # re-add (manifest only, just log it)
1607 for f, args, msg in mresult.getactions(
1610 for f, args, msg in mresult.getactions(
1608 (mergestatemod.ACTION_ADD,), sort=True
1611 (mergestatemod.ACTION_ADD,), sort=True
1609 ):
1612 ):
1610 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1613 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1611 progress.increment(item=f)
1614 progress.increment(item=f)
1612
1615
1613 # re-add/mark as modified (manifest only, just log it)
1616 # re-add/mark as modified (manifest only, just log it)
1614 for f, args, msg in mresult.getactions(
1617 for f, args, msg in mresult.getactions(
1615 (mergestatemod.ACTION_ADD_MODIFIED,), sort=True
1618 (mergestatemod.ACTION_ADD_MODIFIED,), sort=True
1616 ):
1619 ):
1617 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1620 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1618 progress.increment(item=f)
1621 progress.increment(item=f)
1619
1622
1620 # keep (noop, just log it)
1623 # keep (noop, just log it)
1621 for a in mergestatemod.MergeAction.NO_OP_ACTIONS:
1624 for a in mergestatemod.MergeAction.NO_OP_ACTIONS:
1622 for f, args, msg in mresult.getactions((a,), sort=True):
1625 for f, args, msg in mresult.getactions((a,), sort=True):
1623 repo.ui.debug(b" %s: %s -> %s\n" % (f, msg, a.__bytes__()))
1626 repo.ui.debug(b" %s: %s -> %s\n" % (f, msg, a.__bytes__()))
1624 # no progress
1627 # no progress
1625
1628
1626 # directory rename, move local
1629 # directory rename, move local
1627 for f, args, msg in mresult.getactions(
1630 for f, args, msg in mresult.getactions(
1628 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,), sort=True
1631 (mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,), sort=True
1629 ):
1632 ):
1630 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1633 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1631 progress.increment(item=f)
1634 progress.increment(item=f)
1632 f0, flags = args
1635 f0, flags = args
1633 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1636 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1634 wctx[f].audit()
1637 wctx[f].audit()
1635 wctx[f].write(wctx.filectx(f0).data(), flags)
1638 wctx[f].write(wctx.filectx(f0).data(), flags)
1636 wctx[f0].remove()
1639 wctx[f0].remove()
1637
1640
1638 # local directory rename, get
1641 # local directory rename, get
1639 for f, args, msg in mresult.getactions(
1642 for f, args, msg in mresult.getactions(
1640 (mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,), sort=True
1643 (mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,), sort=True
1641 ):
1644 ):
1642 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1645 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1643 progress.increment(item=f)
1646 progress.increment(item=f)
1644 f0, flags = args
1647 f0, flags = args
1645 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1648 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1646 wctx[f].write(mctx.filectx(f0).data(), flags)
1649 wctx[f].write(mctx.filectx(f0).data(), flags)
1647
1650
1648 # exec
1651 # exec
1649 for f, args, msg in mresult.getactions(
1652 for f, args, msg in mresult.getactions(
1650 (mergestatemod.ACTION_EXEC,), sort=True
1653 (mergestatemod.ACTION_EXEC,), sort=True
1651 ):
1654 ):
1652 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1655 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1653 progress.increment(item=f)
1656 progress.increment(item=f)
1654 (flags,) = args
1657 (flags,) = args
1655 wctx[f].audit()
1658 wctx[f].audit()
1656 wctx[f].setflags(b'l' in flags, b'x' in flags)
1659 wctx[f].setflags(b'l' in flags, b'x' in flags)
1657
1660
1658 moves = []
1661 moves = []
1659
1662
1660 # 'cd' and 'dc' actions are treated like other merge conflicts
1663 # 'cd' and 'dc' actions are treated like other merge conflicts
1661 mergeactions = list(
1664 mergeactions = list(
1662 mresult.getactions(
1665 mresult.getactions(
1663 [
1666 [
1664 mergestatemod.ACTION_CHANGED_DELETED,
1667 mergestatemod.ACTION_CHANGED_DELETED,
1665 mergestatemod.ACTION_DELETED_CHANGED,
1668 mergestatemod.ACTION_DELETED_CHANGED,
1666 mergestatemod.ACTION_MERGE,
1669 mergestatemod.ACTION_MERGE,
1667 ],
1670 ],
1668 sort=True,
1671 sort=True,
1669 )
1672 )
1670 )
1673 )
1671 for f, args, msg in mergeactions:
1674 for f, args, msg in mergeactions:
1672 f1, f2, fa, move, anc = args
1675 f1, f2, fa, move, anc = args
1673 if f == b'.hgsubstate': # merged internally
1676 if f == b'.hgsubstate': # merged internally
1674 continue
1677 continue
1675 if f1 is None:
1678 if f1 is None:
1676 fcl = filemerge.absentfilectx(wctx, fa)
1679 fcl = filemerge.absentfilectx(wctx, fa)
1677 else:
1680 else:
1678 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1681 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1679 fcl = wctx[f1]
1682 fcl = wctx[f1]
1680 if f2 is None:
1683 if f2 is None:
1681 fco = filemerge.absentfilectx(mctx, fa)
1684 fco = filemerge.absentfilectx(mctx, fa)
1682 else:
1685 else:
1683 fco = mctx[f2]
1686 fco = mctx[f2]
1684 actx = repo[anc]
1687 actx = repo[anc]
1685 if fa in actx:
1688 if fa in actx:
1686 fca = actx[fa]
1689 fca = actx[fa]
1687 else:
1690 else:
1688 # TODO: move to absentfilectx
1691 # TODO: move to absentfilectx
1689 fca = repo.filectx(f1, fileid=nullrev)
1692 fca = repo.filectx(f1, fileid=nullrev)
1690 ms.add(fcl, fco, fca, f)
1693 ms.add(fcl, fco, fca, f)
1691 if f1 != f and move:
1694 if f1 != f and move:
1692 moves.append(f1)
1695 moves.append(f1)
1693
1696
1694 # remove renamed files after safely stored
1697 # remove renamed files after safely stored
1695 for f in moves:
1698 for f in moves:
1696 if wctx[f].lexists():
1699 if wctx[f].lexists():
1697 repo.ui.debug(b"removing %s\n" % f)
1700 repo.ui.debug(b"removing %s\n" % f)
1698 wctx[f].audit()
1701 wctx[f].audit()
1699 wctx[f].remove()
1702 wctx[f].remove()
1700
1703
1701 # these actions updates the file
1704 # these actions updates the file
1702 updated = mresult.len(
1705 updated = mresult.len(
1703 (
1706 (
1704 mergestatemod.ACTION_GET,
1707 mergestatemod.ACTION_GET,
1705 mergestatemod.ACTION_EXEC,
1708 mergestatemod.ACTION_EXEC,
1706 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1709 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1707 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1710 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1708 )
1711 )
1709 )
1712 )
1710
1713
1711 try:
1714 try:
1712 for f, args, msg in mergeactions:
1715 for f, args, msg in mergeactions:
1713 repo.ui.debug(b" %s: %s -> m\n" % (f, msg))
1716 repo.ui.debug(b" %s: %s -> m\n" % (f, msg))
1714 ms.addcommitinfo(f, {b'merged': b'yes'})
1717 ms.addcommitinfo(f, {b'merged': b'yes'})
1715 progress.increment(item=f)
1718 progress.increment(item=f)
1716 if f == b'.hgsubstate': # subrepo states need updating
1719 if f == b'.hgsubstate': # subrepo states need updating
1717 subrepoutil.submerge(
1720 subrepoutil.submerge(
1718 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1721 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1719 )
1722 )
1720 continue
1723 continue
1721 wctx[f].audit()
1724 wctx[f].audit()
1722 ms.resolve(f, wctx)
1725 ms.resolve(f, wctx)
1723
1726
1724 except error.InterventionRequired:
1727 except error.InterventionRequired:
1725 # If the user has merge.on-failure=halt, catch the error and close the
1728 # If the user has merge.on-failure=halt, catch the error and close the
1726 # merge state "properly".
1729 # merge state "properly".
1727 pass
1730 pass
1728 finally:
1731 finally:
1729 ms.commit()
1732 ms.commit()
1730
1733
1731 unresolved = ms.unresolvedcount()
1734 unresolved = ms.unresolvedcount()
1732
1735
1733 msupdated, msmerged, msremoved = ms.counts()
1736 msupdated, msmerged, msremoved = ms.counts()
1734 updated += msupdated
1737 updated += msupdated
1735 merged += msmerged
1738 merged += msmerged
1736 removed += msremoved
1739 removed += msremoved
1737
1740
1738 extraactions = ms.actions()
1741 extraactions = ms.actions()
1739
1742
1740 progress.complete()
1743 progress.complete()
1741 return (
1744 return (
1742 updateresult(updated, merged, removed, unresolved),
1745 updateresult(updated, merged, removed, unresolved),
1743 getfiledata,
1746 getfiledata,
1744 extraactions,
1747 extraactions,
1745 )
1748 )
1746
1749
1747
1750
1748 def _advertisefsmonitor(repo, num_gets, p1node):
1751 def _advertisefsmonitor(repo, num_gets, p1node):
1749 # Advertise fsmonitor when its presence could be useful.
1752 # Advertise fsmonitor when its presence could be useful.
1750 #
1753 #
1751 # We only advertise when performing an update from an empty working
1754 # We only advertise when performing an update from an empty working
1752 # directory. This typically only occurs during initial clone.
1755 # directory. This typically only occurs during initial clone.
1753 #
1756 #
1754 # We give users a mechanism to disable the warning in case it is
1757 # We give users a mechanism to disable the warning in case it is
1755 # annoying.
1758 # annoying.
1756 #
1759 #
1757 # We only allow on Linux and MacOS because that's where fsmonitor is
1760 # We only allow on Linux and MacOS because that's where fsmonitor is
1758 # considered stable.
1761 # considered stable.
1759 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1762 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1760 fsmonitorthreshold = repo.ui.configint(
1763 fsmonitorthreshold = repo.ui.configint(
1761 b'fsmonitor', b'warn_update_file_count'
1764 b'fsmonitor', b'warn_update_file_count'
1762 )
1765 )
1763 # avoid cycle dirstate -> sparse -> merge -> dirstate
1766 # avoid cycle dirstate -> sparse -> merge -> dirstate
1764 dirstate_rustmod = policy.importrust("dirstate")
1767 dirstate_rustmod = policy.importrust("dirstate")
1765
1768
1766 if dirstate_rustmod is not None:
1769 if dirstate_rustmod is not None:
1767 # When using rust status, fsmonitor becomes necessary at higher sizes
1770 # When using rust status, fsmonitor becomes necessary at higher sizes
1768 fsmonitorthreshold = repo.ui.configint(
1771 fsmonitorthreshold = repo.ui.configint(
1769 b'fsmonitor',
1772 b'fsmonitor',
1770 b'warn_update_file_count_rust',
1773 b'warn_update_file_count_rust',
1771 )
1774 )
1772
1775
1773 try:
1776 try:
1774 # avoid cycle: extensions -> cmdutil -> merge
1777 # avoid cycle: extensions -> cmdutil -> merge
1775 from . import extensions
1778 from . import extensions
1776
1779
1777 extensions.find(b'fsmonitor')
1780 extensions.find(b'fsmonitor')
1778 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1781 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1779 # We intentionally don't look at whether fsmonitor has disabled
1782 # We intentionally don't look at whether fsmonitor has disabled
1780 # itself because a) fsmonitor may have already printed a warning
1783 # itself because a) fsmonitor may have already printed a warning
1781 # b) we only care about the config state here.
1784 # b) we only care about the config state here.
1782 except KeyError:
1785 except KeyError:
1783 fsmonitorenabled = False
1786 fsmonitorenabled = False
1784
1787
1785 if (
1788 if (
1786 fsmonitorwarning
1789 fsmonitorwarning
1787 and not fsmonitorenabled
1790 and not fsmonitorenabled
1788 and p1node == repo.nullid
1791 and p1node == repo.nullid
1789 and num_gets >= fsmonitorthreshold
1792 and num_gets >= fsmonitorthreshold
1790 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1793 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1791 ):
1794 ):
1792 repo.ui.warn(
1795 repo.ui.warn(
1793 _(
1796 _(
1794 b'(warning: large working directory being used without '
1797 b'(warning: large working directory being used without '
1795 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1798 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1796 b'see "hg help -e fsmonitor")\n'
1799 b'see "hg help -e fsmonitor")\n'
1797 )
1800 )
1798 )
1801 )
1799
1802
1800
1803
1801 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1804 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1802 UPDATECHECK_NONE = b'none'
1805 UPDATECHECK_NONE = b'none'
1803 UPDATECHECK_LINEAR = b'linear'
1806 UPDATECHECK_LINEAR = b'linear'
1804 UPDATECHECK_NO_CONFLICT = b'noconflict'
1807 UPDATECHECK_NO_CONFLICT = b'noconflict'
1805
1808
1806
1809
1807 def _update(
1810 def _update(
1808 repo,
1811 repo,
1809 node,
1812 node,
1810 branchmerge,
1813 branchmerge,
1811 force,
1814 force,
1812 ancestor=None,
1815 ancestor=None,
1813 mergeancestor=False,
1816 mergeancestor=False,
1814 labels=None,
1817 labels=None,
1815 matcher=None,
1818 matcher=None,
1816 mergeforce=False,
1819 mergeforce=False,
1817 updatedirstate=True,
1820 updatedirstate=True,
1818 updatecheck=None,
1821 updatecheck=None,
1819 wc=None,
1822 wc=None,
1820 ):
1823 ):
1821 """
1824 """
1822 Perform a merge between the working directory and the given node
1825 Perform a merge between the working directory and the given node
1823
1826
1824 node = the node to update to
1827 node = the node to update to
1825 branchmerge = whether to merge between branches
1828 branchmerge = whether to merge between branches
1826 force = whether to force branch merging or file overwriting
1829 force = whether to force branch merging or file overwriting
1827 matcher = a matcher to filter file lists (dirstate not updated)
1830 matcher = a matcher to filter file lists (dirstate not updated)
1828 mergeancestor = whether it is merging with an ancestor. If true,
1831 mergeancestor = whether it is merging with an ancestor. If true,
1829 we should accept the incoming changes for any prompts that occur.
1832 we should accept the incoming changes for any prompts that occur.
1830 If false, merging with an ancestor (fast-forward) is only allowed
1833 If false, merging with an ancestor (fast-forward) is only allowed
1831 between different named branches. This flag is used by rebase extension
1834 between different named branches. This flag is used by rebase extension
1832 as a temporary fix and should be avoided in general.
1835 as a temporary fix and should be avoided in general.
1833 labels = labels to use for local, other, and base
1836 labels = labels to use for local, other, and base
1834 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1837 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1835 this is True, then 'force' should be True as well.
1838 this is True, then 'force' should be True as well.
1836
1839
1837 The table below shows all the behaviors of the update command given the
1840 The table below shows all the behaviors of the update command given the
1838 -c/--check and -C/--clean or no options, whether the working directory is
1841 -c/--check and -C/--clean or no options, whether the working directory is
1839 dirty, whether a revision is specified, and the relationship of the parent
1842 dirty, whether a revision is specified, and the relationship of the parent
1840 rev to the target rev (linear or not). Match from top first. The -n
1843 rev to the target rev (linear or not). Match from top first. The -n
1841 option doesn't exist on the command line, but represents the
1844 option doesn't exist on the command line, but represents the
1842 experimental.updatecheck=noconflict option.
1845 experimental.updatecheck=noconflict option.
1843
1846
1844 This logic is tested by test-update-branches.t.
1847 This logic is tested by test-update-branches.t.
1845
1848
1846 -c -C -n -m dirty rev linear | result
1849 -c -C -n -m dirty rev linear | result
1847 y y * * * * * | (1)
1850 y y * * * * * | (1)
1848 y * y * * * * | (1)
1851 y * y * * * * | (1)
1849 y * * y * * * | (1)
1852 y * * y * * * | (1)
1850 * y y * * * * | (1)
1853 * y y * * * * | (1)
1851 * y * y * * * | (1)
1854 * y * y * * * | (1)
1852 * * y y * * * | (1)
1855 * * y y * * * | (1)
1853 * * * * * n n | x
1856 * * * * * n n | x
1854 * * * * n * * | ok
1857 * * * * n * * | ok
1855 n n n n y * y | merge
1858 n n n n y * y | merge
1856 n n n n y y n | (2)
1859 n n n n y y n | (2)
1857 n n n y y * * | merge
1860 n n n y y * * | merge
1858 n n y n y * * | merge if no conflict
1861 n n y n y * * | merge if no conflict
1859 n y n n y * * | discard
1862 n y n n y * * | discard
1860 y n n n y * * | (3)
1863 y n n n y * * | (3)
1861
1864
1862 x = can't happen
1865 x = can't happen
1863 * = don't-care
1866 * = don't-care
1864 1 = incompatible options (checked in commands.py)
1867 1 = incompatible options (checked in commands.py)
1865 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1868 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1866 3 = abort: uncommitted changes (checked in commands.py)
1869 3 = abort: uncommitted changes (checked in commands.py)
1867
1870
1868 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1871 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1869 to repo[None] if None is passed.
1872 to repo[None] if None is passed.
1870
1873
1871 Return the same tuple as applyupdates().
1874 Return the same tuple as applyupdates().
1872 """
1875 """
1873 # Avoid cycle.
1876 # Avoid cycle.
1874 from . import sparse
1877 from . import sparse
1875
1878
1876 # This function used to find the default destination if node was None, but
1879 # This function used to find the default destination if node was None, but
1877 # that's now in destutil.py.
1880 # that's now in destutil.py.
1878 assert node is not None
1881 assert node is not None
1879 if not branchmerge and not force:
1882 if not branchmerge and not force:
1880 # TODO: remove the default once all callers that pass branchmerge=False
1883 # TODO: remove the default once all callers that pass branchmerge=False
1881 # and force=False pass a value for updatecheck. We may want to allow
1884 # and force=False pass a value for updatecheck. We may want to allow
1882 # updatecheck='abort' to better suppport some of these callers.
1885 # updatecheck='abort' to better suppport some of these callers.
1883 if updatecheck is None:
1886 if updatecheck is None:
1884 updatecheck = UPDATECHECK_LINEAR
1887 updatecheck = UPDATECHECK_LINEAR
1885 okay = (UPDATECHECK_NONE, UPDATECHECK_LINEAR, UPDATECHECK_NO_CONFLICT)
1888 okay = (UPDATECHECK_NONE, UPDATECHECK_LINEAR, UPDATECHECK_NO_CONFLICT)
1886 if updatecheck not in okay:
1889 if updatecheck not in okay:
1887 msg = r'Invalid updatecheck %r (can accept %r)'
1890 msg = r'Invalid updatecheck %r (can accept %r)'
1888 msg %= (updatecheck, okay)
1891 msg %= (updatecheck, okay)
1889 raise ValueError(msg)
1892 raise ValueError(msg)
1890 if wc is not None and wc.isinmemory():
1893 if wc is not None and wc.isinmemory():
1891 maybe_wlock = util.nullcontextmanager()
1894 maybe_wlock = util.nullcontextmanager()
1892 else:
1895 else:
1893 maybe_wlock = repo.wlock()
1896 maybe_wlock = repo.wlock()
1894 with maybe_wlock:
1897 with maybe_wlock:
1895 if wc is None:
1898 if wc is None:
1896 wc = repo[None]
1899 wc = repo[None]
1897 pl = wc.parents()
1900 pl = wc.parents()
1898 p1 = pl[0]
1901 p1 = pl[0]
1899 p2 = repo[node]
1902 p2 = repo[node]
1900 if ancestor is not None:
1903 if ancestor is not None:
1901 pas = [repo[ancestor]]
1904 pas = [repo[ancestor]]
1902 else:
1905 else:
1903 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1906 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1904 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1907 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1905 pas = [repo[anc] for anc in (sorted(cahs) or [repo.nullid])]
1908 pas = [repo[anc] for anc in (sorted(cahs) or [repo.nullid])]
1906 else:
1909 else:
1907 pas = [p1.ancestor(p2, warn=branchmerge)]
1910 pas = [p1.ancestor(p2, warn=branchmerge)]
1908
1911
1909 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1912 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1910
1913
1911 overwrite = force and not branchmerge
1914 overwrite = force and not branchmerge
1912 ### check phase
1915 ### check phase
1913 if not overwrite:
1916 if not overwrite:
1914 if len(pl) > 1:
1917 if len(pl) > 1:
1915 raise error.StateError(_(b"outstanding uncommitted merge"))
1918 raise error.StateError(_(b"outstanding uncommitted merge"))
1916 ms = wc.mergestate()
1919 ms = wc.mergestate()
1917 if ms.unresolvedcount():
1920 if ms.unresolvedcount():
1918 msg = _(b"outstanding merge conflicts")
1921 msg = _(b"outstanding merge conflicts")
1919 hint = _(b"use 'hg resolve' to resolve")
1922 hint = _(b"use 'hg resolve' to resolve")
1920 raise error.StateError(msg, hint=hint)
1923 raise error.StateError(msg, hint=hint)
1921 if branchmerge:
1924 if branchmerge:
1922 m_a = _(b"merging with a working directory ancestor has no effect")
1925 m_a = _(b"merging with a working directory ancestor has no effect")
1923 if pas == [p2]:
1926 if pas == [p2]:
1924 raise error.Abort(m_a)
1927 raise error.Abort(m_a)
1925 elif pas == [p1]:
1928 elif pas == [p1]:
1926 if not mergeancestor and wc.branch() == p2.branch():
1929 if not mergeancestor and wc.branch() == p2.branch():
1927 msg = _(b"nothing to merge")
1930 msg = _(b"nothing to merge")
1928 hint = _(b"use 'hg update' or check 'hg heads'")
1931 hint = _(b"use 'hg update' or check 'hg heads'")
1929 raise error.Abort(msg, hint=hint)
1932 raise error.Abort(msg, hint=hint)
1930 if not force and (wc.files() or wc.deleted()):
1933 if not force and (wc.files() or wc.deleted()):
1931 msg = _(b"uncommitted changes")
1934 msg = _(b"uncommitted changes")
1932 hint = _(b"use 'hg status' to list changes")
1935 hint = _(b"use 'hg status' to list changes")
1933 raise error.StateError(msg, hint=hint)
1936 raise error.StateError(msg, hint=hint)
1934 if not wc.isinmemory():
1937 if not wc.isinmemory():
1935 for s in sorted(wc.substate):
1938 for s in sorted(wc.substate):
1936 wc.sub(s).bailifchanged()
1939 wc.sub(s).bailifchanged()
1937
1940
1938 elif not overwrite:
1941 elif not overwrite:
1939 if p1 == p2: # no-op update
1942 if p1 == p2: # no-op update
1940 # call the hooks and exit early
1943 # call the hooks and exit early
1941 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1944 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1942 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1945 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1943 return updateresult(0, 0, 0, 0)
1946 return updateresult(0, 0, 0, 0)
1944
1947
1945 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1948 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1946 [p1],
1949 [p1],
1947 [p2],
1950 [p2],
1948 ): # nonlinear
1951 ): # nonlinear
1949 dirty = wc.dirty(missing=True)
1952 dirty = wc.dirty(missing=True)
1950 if dirty:
1953 if dirty:
1951 # Branching is a bit strange to ensure we do the minimal
1954 # Branching is a bit strange to ensure we do the minimal
1952 # amount of call to obsutil.foreground.
1955 # amount of call to obsutil.foreground.
1953 foreground = obsutil.foreground(repo, [p1.node()])
1956 foreground = obsutil.foreground(repo, [p1.node()])
1954 # note: the <node> variable contains a random identifier
1957 # note: the <node> variable contains a random identifier
1955 if repo[node].node() in foreground:
1958 if repo[node].node() in foreground:
1956 pass # allow updating to successors
1959 pass # allow updating to successors
1957 else:
1960 else:
1958 msg = _(b"uncommitted changes")
1961 msg = _(b"uncommitted changes")
1959 hint = _(b"commit or update --clean to discard changes")
1962 hint = _(b"commit or update --clean to discard changes")
1960 raise error.UpdateAbort(msg, hint=hint)
1963 raise error.UpdateAbort(msg, hint=hint)
1961 else:
1964 else:
1962 # Allow jumping branches if clean and specific rev given
1965 # Allow jumping branches if clean and specific rev given
1963 pass
1966 pass
1964
1967
1965 if overwrite:
1968 if overwrite:
1966 pas = [wc]
1969 pas = [wc]
1967 elif not branchmerge:
1970 elif not branchmerge:
1968 pas = [p1]
1971 pas = [p1]
1969
1972
1970 # deprecated config: merge.followcopies
1973 # deprecated config: merge.followcopies
1971 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1974 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1972 if overwrite:
1975 if overwrite:
1973 followcopies = False
1976 followcopies = False
1974 elif not pas[0]:
1977 elif not pas[0]:
1975 followcopies = False
1978 followcopies = False
1976 if not branchmerge and not wc.dirty(missing=True):
1979 if not branchmerge and not wc.dirty(missing=True):
1977 followcopies = False
1980 followcopies = False
1978
1981
1979 ### calculate phase
1982 ### calculate phase
1980 mresult = calculateupdates(
1983 mresult = calculateupdates(
1981 repo,
1984 repo,
1982 wc,
1985 wc,
1983 p2,
1986 p2,
1984 pas,
1987 pas,
1985 branchmerge,
1988 branchmerge,
1986 force,
1989 force,
1987 mergeancestor,
1990 mergeancestor,
1988 followcopies,
1991 followcopies,
1989 matcher=matcher,
1992 matcher=matcher,
1990 mergeforce=mergeforce,
1993 mergeforce=mergeforce,
1991 )
1994 )
1992
1995
1993 if updatecheck == UPDATECHECK_NO_CONFLICT:
1996 if updatecheck == UPDATECHECK_NO_CONFLICT:
1994 if mresult.hasconflicts():
1997 if mresult.hasconflicts():
1995 msg = _(b"conflicting changes")
1998 msg = _(b"conflicting changes")
1996 hint = _(b"commit or update --clean to discard changes")
1999 hint = _(b"commit or update --clean to discard changes")
1997 raise error.StateError(msg, hint=hint)
2000 raise error.StateError(msg, hint=hint)
1998
2001
1999 # Prompt and create actions. Most of this is in the resolve phase
2002 # Prompt and create actions. Most of this is in the resolve phase
2000 # already, but we can't handle .hgsubstate in filemerge or
2003 # already, but we can't handle .hgsubstate in filemerge or
2001 # subrepoutil.submerge yet so we have to keep prompting for it.
2004 # subrepoutil.submerge yet so we have to keep prompting for it.
2002 vals = mresult.getfile(b'.hgsubstate')
2005 vals = mresult.getfile(b'.hgsubstate')
2003 if vals:
2006 if vals:
2004 f = b'.hgsubstate'
2007 f = b'.hgsubstate'
2005 m, args, msg = vals
2008 m, args, msg = vals
2006 prompts = filemerge.partextras(labels)
2009 prompts = filemerge.partextras(labels)
2007 prompts[b'f'] = f
2010 prompts[b'f'] = f
2008 if m == mergestatemod.ACTION_CHANGED_DELETED:
2011 if m == mergestatemod.ACTION_CHANGED_DELETED:
2009 if repo.ui.promptchoice(
2012 if repo.ui.promptchoice(
2010 _(
2013 _(
2011 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2014 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2012 b"use (c)hanged version or (d)elete?"
2015 b"use (c)hanged version or (d)elete?"
2013 b"$$ &Changed $$ &Delete"
2016 b"$$ &Changed $$ &Delete"
2014 )
2017 )
2015 % prompts,
2018 % prompts,
2016 0,
2019 0,
2017 ):
2020 ):
2018 mresult.addfile(
2021 mresult.addfile(
2019 f,
2022 f,
2020 mergestatemod.ACTION_REMOVE,
2023 mergestatemod.ACTION_REMOVE,
2021 None,
2024 None,
2022 b'prompt delete',
2025 b'prompt delete',
2023 )
2026 )
2024 elif f in p1:
2027 elif f in p1:
2025 mresult.addfile(
2028 mresult.addfile(
2026 f,
2029 f,
2027 mergestatemod.ACTION_ADD_MODIFIED,
2030 mergestatemod.ACTION_ADD_MODIFIED,
2028 None,
2031 None,
2029 b'prompt keep',
2032 b'prompt keep',
2030 )
2033 )
2031 else:
2034 else:
2032 mresult.addfile(
2035 mresult.addfile(
2033 f,
2036 f,
2034 mergestatemod.ACTION_ADD,
2037 mergestatemod.ACTION_ADD,
2035 None,
2038 None,
2036 b'prompt keep',
2039 b'prompt keep',
2037 )
2040 )
2038 elif m == mergestatemod.ACTION_DELETED_CHANGED:
2041 elif m == mergestatemod.ACTION_DELETED_CHANGED:
2039 f1, f2, fa, move, anc = args
2042 f1, f2, fa, move, anc = args
2040 flags = p2[f2].flags()
2043 flags = p2[f2].flags()
2041 if (
2044 if (
2042 repo.ui.promptchoice(
2045 repo.ui.promptchoice(
2043 _(
2046 _(
2044 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2047 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2045 b"use (c)hanged version or leave (d)eleted?"
2048 b"use (c)hanged version or leave (d)eleted?"
2046 b"$$ &Changed $$ &Deleted"
2049 b"$$ &Changed $$ &Deleted"
2047 )
2050 )
2048 % prompts,
2051 % prompts,
2049 0,
2052 0,
2050 )
2053 )
2051 == 0
2054 == 0
2052 ):
2055 ):
2053 mresult.addfile(
2056 mresult.addfile(
2054 f,
2057 f,
2055 mergestatemod.ACTION_GET,
2058 mergestatemod.ACTION_GET,
2056 (flags, False),
2059 (flags, False),
2057 b'prompt recreating',
2060 b'prompt recreating',
2058 )
2061 )
2059 else:
2062 else:
2060 mresult.removefile(f)
2063 mresult.removefile(f)
2061
2064
2062 if not util.fscasesensitive(repo.path):
2065 if not util.fscasesensitive(repo.path):
2063 # check collision between files only in p2 for clean update
2066 # check collision between files only in p2 for clean update
2064 if not branchmerge and (
2067 if not branchmerge and (
2065 force or not wc.dirty(missing=True, branch=False)
2068 force or not wc.dirty(missing=True, branch=False)
2066 ):
2069 ):
2067 _checkcollision(repo, p2.manifest(), None)
2070 _checkcollision(repo, p2.manifest(), None)
2068 else:
2071 else:
2069 _checkcollision(repo, wc.manifest(), mresult)
2072 _checkcollision(repo, wc.manifest(), mresult)
2070
2073
2071 # divergent renames
2074 # divergent renames
2072 for f, fl in sorted(mresult.diverge.items()):
2075 for f, fl in sorted(mresult.diverge.items()):
2073 repo.ui.warn(
2076 repo.ui.warn(
2074 _(
2077 _(
2075 b"note: possible conflict - %s was renamed "
2078 b"note: possible conflict - %s was renamed "
2076 b"multiple times to:\n"
2079 b"multiple times to:\n"
2077 )
2080 )
2078 % f
2081 % f
2079 )
2082 )
2080 for nf in sorted(fl):
2083 for nf in sorted(fl):
2081 repo.ui.warn(b" %s\n" % nf)
2084 repo.ui.warn(b" %s\n" % nf)
2082
2085
2083 # rename and delete
2086 # rename and delete
2084 for f, fl in sorted(mresult.renamedelete.items()):
2087 for f, fl in sorted(mresult.renamedelete.items()):
2085 repo.ui.warn(
2088 repo.ui.warn(
2086 _(
2089 _(
2087 b"note: possible conflict - %s was deleted "
2090 b"note: possible conflict - %s was deleted "
2088 b"and renamed to:\n"
2091 b"and renamed to:\n"
2089 )
2092 )
2090 % f
2093 % f
2091 )
2094 )
2092 for nf in sorted(fl):
2095 for nf in sorted(fl):
2093 repo.ui.warn(b" %s\n" % nf)
2096 repo.ui.warn(b" %s\n" % nf)
2094
2097
2095 ### apply phase
2098 ### apply phase
2096 if not branchmerge: # just jump to the new rev
2099 if not branchmerge: # just jump to the new rev
2097 fp1, fp2, xp1, xp2 = fp2, repo.nullid, xp2, b''
2100 fp1, fp2, xp1, xp2 = fp2, repo.nullid, xp2, b''
2098 # If we're doing a partial update, we need to skip updating
2101 # If we're doing a partial update, we need to skip updating
2099 # the dirstate.
2102 # the dirstate.
2100 always = matcher is None or matcher.always()
2103 always = matcher is None or matcher.always()
2101 updatedirstate = updatedirstate and always and not wc.isinmemory()
2104 updatedirstate = updatedirstate and always and not wc.isinmemory()
2102 if updatedirstate:
2105 if updatedirstate:
2103 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2106 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2104 # note that we're in the middle of an update
2107 # note that we're in the middle of an update
2105 repo.vfs.write(b'updatestate', p2.hex())
2108 repo.vfs.write(b'updatestate', p2.hex())
2106
2109
2107 _advertisefsmonitor(
2110 _advertisefsmonitor(
2108 repo, mresult.len((mergestatemod.ACTION_GET,)), p1.node()
2111 repo, mresult.len((mergestatemod.ACTION_GET,)), p1.node()
2109 )
2112 )
2110
2113
2111 wantfiledata = updatedirstate and not branchmerge
2114 wantfiledata = updatedirstate and not branchmerge
2112 stats, getfiledata, extraactions = applyupdates(
2115 stats, getfiledata, extraactions = applyupdates(
2113 repo,
2116 repo,
2114 mresult,
2117 mresult,
2115 wc,
2118 wc,
2116 p2,
2119 p2,
2117 overwrite,
2120 overwrite,
2118 wantfiledata,
2121 wantfiledata,
2119 labels=labels,
2122 labels=labels,
2120 )
2123 )
2121
2124
2122 if updatedirstate:
2125 if updatedirstate:
2123 if extraactions:
2126 if extraactions:
2124 for k, acts in extraactions.items():
2127 for k, acts in extraactions.items():
2125 for a in acts:
2128 for a in acts:
2126 mresult.addfile(a[0], k, *a[1:])
2129 mresult.addfile(a[0], k, *a[1:])
2127 if k == mergestatemod.ACTION_GET and wantfiledata:
2130 if k == mergestatemod.ACTION_GET and wantfiledata:
2128 # no filedata until mergestate is updated to provide it
2131 # no filedata until mergestate is updated to provide it
2129 for a in acts:
2132 for a in acts:
2130 getfiledata[a[0]] = None
2133 getfiledata[a[0]] = None
2131
2134
2132 assert len(getfiledata) == (
2135 assert len(getfiledata) == (
2133 mresult.len((mergestatemod.ACTION_GET,)) if wantfiledata else 0
2136 mresult.len((mergestatemod.ACTION_GET,)) if wantfiledata else 0
2134 )
2137 )
2135 with repo.dirstate.parentchange():
2138 with repo.dirstate.parentchange():
2136 ### Filter Filedata
2139 ### Filter Filedata
2137 #
2140 #
2138 # We gathered "cache" information for the clean file while
2141 # We gathered "cache" information for the clean file while
2139 # updating them: mtime, size and mode.
2142 # updating them: mtime, size and mode.
2140 #
2143 #
2141 # At the time this comment is written, they are various issues
2144 # At the time this comment is written, they are various issues
2142 # with how we gather the `mode` and `mtime` information (see
2145 # with how we gather the `mode` and `mtime` information (see
2143 # the comment in `batchget`).
2146 # the comment in `batchget`).
2144 #
2147 #
2145 # We are going to smooth one of this issue here : mtime ambiguity.
2148 # We are going to smooth one of this issue here : mtime ambiguity.
2146 #
2149 #
2147 # i.e. even if the mtime gathered during `batchget` was
2150 # i.e. even if the mtime gathered during `batchget` was
2148 # correct[1] a change happening right after it could change the
2151 # correct[1] a change happening right after it could change the
2149 # content while keeping the same mtime[2].
2152 # content while keeping the same mtime[2].
2150 #
2153 #
2151 # When we reach the current code, the "on disk" part of the
2154 # When we reach the current code, the "on disk" part of the
2152 # update operation is finished. We still assume that no other
2155 # update operation is finished. We still assume that no other
2153 # process raced that "on disk" part, but we want to at least
2156 # process raced that "on disk" part, but we want to at least
2154 # prevent later file change to alter the content of the file
2157 # prevent later file change to alter the content of the file
2155 # right after the update operation. So quickly that the same
2158 # right after the update operation. So quickly that the same
2156 # mtime is record for the operation.
2159 # mtime is record for the operation.
2157 # To prevent such ambiguity to happens, we will only keep the
2160 # To prevent such ambiguity to happens, we will only keep the
2158 # "file data" for files with mtime that are stricly in the past,
2161 # "file data" for files with mtime that are stricly in the past,
2159 # i.e. whose mtime is strictly lower than the current time.
2162 # i.e. whose mtime is strictly lower than the current time.
2160 #
2163 #
2161 # This protect us from race conditions from operation that could
2164 # This protect us from race conditions from operation that could
2162 # run right after this one, especially other Mercurial
2165 # run right after this one, especially other Mercurial
2163 # operation that could be waiting for the wlock to touch files
2166 # operation that could be waiting for the wlock to touch files
2164 # content and the dirstate.
2167 # content and the dirstate.
2165 #
2168 #
2166 # In an ideal world, we could only get reliable information in
2169 # In an ideal world, we could only get reliable information in
2167 # `getfiledata` (from `getbatch`), however the current approach
2170 # `getfiledata` (from `getbatch`), however the current approach
2168 # have been a successful compromise since many years.
2171 # have been a successful compromise since many years.
2169 #
2172 #
2170 # At the time this comment is written, not using any "cache"
2173 # At the time this comment is written, not using any "cache"
2171 # file data at all here would not be viable. As it would result is
2174 # file data at all here would not be viable. As it would result is
2172 # a very large amount of work (equivalent to the previous `hg
2175 # a very large amount of work (equivalent to the previous `hg
2173 # update` during the next status after an update).
2176 # update` during the next status after an update).
2174 #
2177 #
2175 # [1] the current code cannot grantee that the `mtime` and
2178 # [1] the current code cannot grantee that the `mtime` and
2176 # `mode` are correct, but the result is "okay in practice".
2179 # `mode` are correct, but the result is "okay in practice".
2177 # (see the comment in `batchget`). #
2180 # (see the comment in `batchget`). #
2178 #
2181 #
2179 # [2] using nano-second precision can greatly help here because
2182 # [2] using nano-second precision can greatly help here because
2180 # it makes the "different write with same mtime" issue
2183 # it makes the "different write with same mtime" issue
2181 # virtually vanish. However, dirstate v1 cannot store such
2184 # virtually vanish. However, dirstate v1 cannot store such
2182 # precision and a bunch of python-runtime, operating-system and
2185 # precision and a bunch of python-runtime, operating-system and
2183 # filesystem does not provide use with such precision, so we
2186 # filesystem does not provide use with such precision, so we
2184 # have to operate as if it wasn't available.
2187 # have to operate as if it wasn't available.
2185 if getfiledata:
2188 if getfiledata:
2186 ambiguous_mtime = {}
2189 ambiguous_mtime = {}
2187 now = timestamp.get_fs_now(repo.vfs)
2190 now = timestamp.get_fs_now(repo.vfs)
2188 if now is None:
2191 if now is None:
2189 # we can't write to the FS, so we won't actually update
2192 # we can't write to the FS, so we won't actually update
2190 # the dirstate content anyway, no need to put cache
2193 # the dirstate content anyway, no need to put cache
2191 # information.
2194 # information.
2192 getfiledata = None
2195 getfiledata = None
2193 else:
2196 else:
2194 now_sec = now[0]
2197 now_sec = now[0]
2195 for f, m in getfiledata.items():
2198 for f, m in getfiledata.items():
2196 if m is not None and m[2][0] >= now_sec:
2199 if m is not None and m[2][0] >= now_sec:
2197 ambiguous_mtime[f] = (m[0], m[1], None)
2200 ambiguous_mtime[f] = (m[0], m[1], None)
2198 for f, m in ambiguous_mtime.items():
2201 for f, m in ambiguous_mtime.items():
2199 getfiledata[f] = m
2202 getfiledata[f] = m
2200
2203
2201 repo.setparents(fp1, fp2)
2204 repo.setparents(fp1, fp2)
2202 mergestatemod.recordupdates(
2205 mergestatemod.recordupdates(
2203 repo, mresult.actionsdict, branchmerge, getfiledata
2206 repo, mresult.actionsdict, branchmerge, getfiledata
2204 )
2207 )
2205 # update completed, clear state
2208 # update completed, clear state
2206 util.unlink(repo.vfs.join(b'updatestate'))
2209 util.unlink(repo.vfs.join(b'updatestate'))
2207
2210
2208 if not branchmerge:
2211 if not branchmerge:
2209 repo.dirstate.setbranch(p2.branch())
2212 repo.dirstate.setbranch(p2.branch())
2210
2213
2211 # If we're updating to a location, clean up any stale temporary includes
2214 # If we're updating to a location, clean up any stale temporary includes
2212 # (ex: this happens during hg rebase --abort).
2215 # (ex: this happens during hg rebase --abort).
2213 if not branchmerge:
2216 if not branchmerge:
2214 sparse.prunetemporaryincludes(repo)
2217 sparse.prunetemporaryincludes(repo)
2215
2218
2216 if updatedirstate:
2219 if updatedirstate:
2217 repo.hook(
2220 repo.hook(
2218 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2221 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2219 )
2222 )
2220 return stats
2223 return stats
2221
2224
2222
2225
2223 def merge(ctx, labels=None, force=False, wc=None):
2226 def merge(ctx, labels=None, force=False, wc=None):
2224 """Merge another topological branch into the working copy.
2227 """Merge another topological branch into the working copy.
2225
2228
2226 force = whether the merge was run with 'merge --force' (deprecated)
2229 force = whether the merge was run with 'merge --force' (deprecated)
2227 """
2230 """
2228
2231
2229 return _update(
2232 return _update(
2230 ctx.repo(),
2233 ctx.repo(),
2231 ctx.rev(),
2234 ctx.rev(),
2232 labels=labels,
2235 labels=labels,
2233 branchmerge=True,
2236 branchmerge=True,
2234 force=force,
2237 force=force,
2235 mergeforce=force,
2238 mergeforce=force,
2236 wc=wc,
2239 wc=wc,
2237 )
2240 )
2238
2241
2239
2242
2240 def update(ctx, updatecheck=None, wc=None):
2243 def update(ctx, updatecheck=None, wc=None):
2241 """Do a regular update to the given commit, aborting if there are conflicts.
2244 """Do a regular update to the given commit, aborting if there are conflicts.
2242
2245
2243 The 'updatecheck' argument can be used to control what to do in case of
2246 The 'updatecheck' argument can be used to control what to do in case of
2244 conflicts.
2247 conflicts.
2245
2248
2246 Note: This is a new, higher-level update() than the one that used to exist
2249 Note: This is a new, higher-level update() than the one that used to exist
2247 in this module. That function is now called _update(). You can hopefully
2250 in this module. That function is now called _update(). You can hopefully
2248 replace your callers to use this new update(), or clean_update(), merge(),
2251 replace your callers to use this new update(), or clean_update(), merge(),
2249 revert_to(), or graft().
2252 revert_to(), or graft().
2250 """
2253 """
2251 return _update(
2254 return _update(
2252 ctx.repo(),
2255 ctx.repo(),
2253 ctx.rev(),
2256 ctx.rev(),
2254 branchmerge=False,
2257 branchmerge=False,
2255 force=False,
2258 force=False,
2256 labels=[b'working copy', b'destination', b'working copy parent'],
2259 labels=[b'working copy', b'destination', b'working copy parent'],
2257 updatecheck=updatecheck,
2260 updatecheck=updatecheck,
2258 wc=wc,
2261 wc=wc,
2259 )
2262 )
2260
2263
2261
2264
2262 def clean_update(ctx, wc=None):
2265 def clean_update(ctx, wc=None):
2263 """Do a clean update to the given commit.
2266 """Do a clean update to the given commit.
2264
2267
2265 This involves updating to the commit and discarding any changes in the
2268 This involves updating to the commit and discarding any changes in the
2266 working copy.
2269 working copy.
2267 """
2270 """
2268 return _update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2271 return _update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2269
2272
2270
2273
2271 def revert_to(ctx, matcher=None, wc=None):
2274 def revert_to(ctx, matcher=None, wc=None):
2272 """Revert the working copy to the given commit.
2275 """Revert the working copy to the given commit.
2273
2276
2274 The working copy will keep its current parent(s) but its content will
2277 The working copy will keep its current parent(s) but its content will
2275 be the same as in the given commit.
2278 be the same as in the given commit.
2276 """
2279 """
2277
2280
2278 return _update(
2281 return _update(
2279 ctx.repo(),
2282 ctx.repo(),
2280 ctx.rev(),
2283 ctx.rev(),
2281 branchmerge=False,
2284 branchmerge=False,
2282 force=True,
2285 force=True,
2283 updatedirstate=False,
2286 updatedirstate=False,
2284 matcher=matcher,
2287 matcher=matcher,
2285 wc=wc,
2288 wc=wc,
2286 )
2289 )
2287
2290
2288
2291
2289 def graft(
2292 def graft(
2290 repo,
2293 repo,
2291 ctx,
2294 ctx,
2292 base=None,
2295 base=None,
2293 labels=None,
2296 labels=None,
2294 keepparent=False,
2297 keepparent=False,
2295 keepconflictparent=False,
2298 keepconflictparent=False,
2296 wctx=None,
2299 wctx=None,
2297 ):
2300 ):
2298 """Do a graft-like merge.
2301 """Do a graft-like merge.
2299
2302
2300 This is a merge where the merge ancestor is chosen such that one
2303 This is a merge where the merge ancestor is chosen such that one
2301 or more changesets are grafted onto the current changeset. In
2304 or more changesets are grafted onto the current changeset. In
2302 addition to the merge, this fixes up the dirstate to include only
2305 addition to the merge, this fixes up the dirstate to include only
2303 a single parent (if keepparent is False) and tries to duplicate any
2306 a single parent (if keepparent is False) and tries to duplicate any
2304 renames/copies appropriately.
2307 renames/copies appropriately.
2305
2308
2306 ctx - changeset to rebase
2309 ctx - changeset to rebase
2307 base - merge base, or ctx.p1() if not specified
2310 base - merge base, or ctx.p1() if not specified
2308 labels - merge labels eg ['local', 'graft']
2311 labels - merge labels eg ['local', 'graft']
2309 keepparent - keep second parent if any
2312 keepparent - keep second parent if any
2310 keepconflictparent - if unresolved, keep parent used for the merge
2313 keepconflictparent - if unresolved, keep parent used for the merge
2311
2314
2312 """
2315 """
2313 # If we're grafting a descendant onto an ancestor, be sure to pass
2316 # If we're grafting a descendant onto an ancestor, be sure to pass
2314 # mergeancestor=True to update. This does two things: 1) allows the merge if
2317 # mergeancestor=True to update. This does two things: 1) allows the merge if
2315 # the destination is the same as the parent of the ctx (so we can use graft
2318 # the destination is the same as the parent of the ctx (so we can use graft
2316 # to copy commits), and 2) informs update that the incoming changes are
2319 # to copy commits), and 2) informs update that the incoming changes are
2317 # newer than the destination so it doesn't prompt about "remote changed foo
2320 # newer than the destination so it doesn't prompt about "remote changed foo
2318 # which local deleted".
2321 # which local deleted".
2319 # We also pass mergeancestor=True when base is the same revision as p1. 2)
2322 # We also pass mergeancestor=True when base is the same revision as p1. 2)
2320 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
2323 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
2321 wctx = wctx or repo[None]
2324 wctx = wctx or repo[None]
2322 pctx = wctx.p1()
2325 pctx = wctx.p1()
2323 base = base or ctx.p1()
2326 base = base or ctx.p1()
2324 mergeancestor = (
2327 mergeancestor = (
2325 repo.changelog.isancestor(pctx.node(), ctx.node())
2328 repo.changelog.isancestor(pctx.node(), ctx.node())
2326 or pctx.rev() == base.rev()
2329 or pctx.rev() == base.rev()
2327 )
2330 )
2328
2331
2329 stats = _update(
2332 stats = _update(
2330 repo,
2333 repo,
2331 ctx.node(),
2334 ctx.node(),
2332 True,
2335 True,
2333 True,
2336 True,
2334 base.node(),
2337 base.node(),
2335 mergeancestor=mergeancestor,
2338 mergeancestor=mergeancestor,
2336 labels=labels,
2339 labels=labels,
2337 wc=wctx,
2340 wc=wctx,
2338 )
2341 )
2339
2342
2340 if keepconflictparent and stats.unresolvedcount:
2343 if keepconflictparent and stats.unresolvedcount:
2341 pother = ctx.node()
2344 pother = ctx.node()
2342 else:
2345 else:
2343 pother = repo.nullid
2346 pother = repo.nullid
2344 parents = ctx.parents()
2347 parents = ctx.parents()
2345 if keepparent and len(parents) == 2 and base in parents:
2348 if keepparent and len(parents) == 2 and base in parents:
2346 parents.remove(base)
2349 parents.remove(base)
2347 pother = parents[0].node()
2350 pother = parents[0].node()
2348 # Never set both parents equal to each other
2351 # Never set both parents equal to each other
2349 if pother == pctx.node():
2352 if pother == pctx.node():
2350 pother = repo.nullid
2353 pother = repo.nullid
2351
2354
2352 if wctx.isinmemory():
2355 if wctx.isinmemory():
2353 wctx.setparents(pctx.node(), pother)
2356 wctx.setparents(pctx.node(), pother)
2354 # fix up dirstate for copies and renames
2357 # fix up dirstate for copies and renames
2355 copies.graftcopies(wctx, ctx, base)
2358 copies.graftcopies(wctx, ctx, base)
2356 else:
2359 else:
2357 with repo.dirstate.parentchange():
2360 with repo.dirstate.parentchange():
2358 repo.setparents(pctx.node(), pother)
2361 repo.setparents(pctx.node(), pother)
2359 repo.dirstate.write(repo.currenttransaction())
2362 repo.dirstate.write(repo.currenttransaction())
2360 # fix up dirstate for copies and renames
2363 # fix up dirstate for copies and renames
2361 copies.graftcopies(wctx, ctx, base)
2364 copies.graftcopies(wctx, ctx, base)
2362 return stats
2365 return stats
2363
2366
2364
2367
2365 def back_out(ctx, parent=None, wc=None):
2368 def back_out(ctx, parent=None, wc=None):
2366 if parent is None:
2369 if parent is None:
2367 if ctx.p2() is not None:
2370 if ctx.p2() is not None:
2368 msg = b"must specify parent of merge commit to back out"
2371 msg = b"must specify parent of merge commit to back out"
2369 raise error.ProgrammingError(msg)
2372 raise error.ProgrammingError(msg)
2370 parent = ctx.p1()
2373 parent = ctx.p1()
2371 return _update(
2374 return _update(
2372 ctx.repo(),
2375 ctx.repo(),
2373 parent,
2376 parent,
2374 branchmerge=True,
2377 branchmerge=True,
2375 force=True,
2378 force=True,
2376 ancestor=ctx.node(),
2379 ancestor=ctx.node(),
2377 mergeancestor=False,
2380 mergeancestor=False,
2378 )
2381 )
2379
2382
2380
2383
2381 def purge(
2384 def purge(
2382 repo,
2385 repo,
2383 matcher,
2386 matcher,
2384 unknown=True,
2387 unknown=True,
2385 ignored=False,
2388 ignored=False,
2386 removeemptydirs=True,
2389 removeemptydirs=True,
2387 removefiles=True,
2390 removefiles=True,
2388 abortonerror=False,
2391 abortonerror=False,
2389 noop=False,
2392 noop=False,
2390 confirm=False,
2393 confirm=False,
2391 ):
2394 ):
2392 """Purge the working directory of untracked files.
2395 """Purge the working directory of untracked files.
2393
2396
2394 ``matcher`` is a matcher configured to scan the working directory -
2397 ``matcher`` is a matcher configured to scan the working directory -
2395 potentially a subset.
2398 potentially a subset.
2396
2399
2397 ``unknown`` controls whether unknown files should be purged.
2400 ``unknown`` controls whether unknown files should be purged.
2398
2401
2399 ``ignored`` controls whether ignored files should be purged.
2402 ``ignored`` controls whether ignored files should be purged.
2400
2403
2401 ``removeemptydirs`` controls whether empty directories should be removed.
2404 ``removeemptydirs`` controls whether empty directories should be removed.
2402
2405
2403 ``removefiles`` controls whether files are removed.
2406 ``removefiles`` controls whether files are removed.
2404
2407
2405 ``abortonerror`` causes an exception to be raised if an error occurs
2408 ``abortonerror`` causes an exception to be raised if an error occurs
2406 deleting a file or directory.
2409 deleting a file or directory.
2407
2410
2408 ``noop`` controls whether to actually remove files. If not defined, actions
2411 ``noop`` controls whether to actually remove files. If not defined, actions
2409 will be taken.
2412 will be taken.
2410
2413
2411 ``confirm`` ask confirmation before actually removing anything.
2414 ``confirm`` ask confirmation before actually removing anything.
2412
2415
2413 Returns an iterable of relative paths in the working directory that were
2416 Returns an iterable of relative paths in the working directory that were
2414 or would be removed.
2417 or would be removed.
2415 """
2418 """
2416
2419
2417 def remove(removefn, path):
2420 def remove(removefn, path):
2418 try:
2421 try:
2419 removefn(path)
2422 removefn(path)
2420 except OSError:
2423 except OSError:
2421 m = _(b'%s cannot be removed') % path
2424 m = _(b'%s cannot be removed') % path
2422 if abortonerror:
2425 if abortonerror:
2423 raise error.Abort(m)
2426 raise error.Abort(m)
2424 else:
2427 else:
2425 repo.ui.warn(_(b'warning: %s\n') % m)
2428 repo.ui.warn(_(b'warning: %s\n') % m)
2426
2429
2427 # There's no API to copy a matcher. So mutate the passed matcher and
2430 # There's no API to copy a matcher. So mutate the passed matcher and
2428 # restore it when we're done.
2431 # restore it when we're done.
2429 oldtraversedir = matcher.traversedir
2432 oldtraversedir = matcher.traversedir
2430
2433
2431 res = []
2434 res = []
2432
2435
2433 try:
2436 try:
2434 if removeemptydirs:
2437 if removeemptydirs:
2435 directories = []
2438 directories = []
2436 matcher.traversedir = directories.append
2439 matcher.traversedir = directories.append
2437
2440
2438 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2441 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2439
2442
2440 if confirm:
2443 if confirm:
2441 msg = None
2444 msg = None
2442 nb_ignored = len(status.ignored)
2445 nb_ignored = len(status.ignored)
2443 nb_unknown = len(status.unknown)
2446 nb_unknown = len(status.unknown)
2444 if nb_unknown and nb_ignored:
2447 if nb_unknown and nb_ignored:
2445 msg = _(b"permanently delete %d unknown and %d ignored files?")
2448 msg = _(b"permanently delete %d unknown and %d ignored files?")
2446 msg %= (nb_unknown, nb_ignored)
2449 msg %= (nb_unknown, nb_ignored)
2447 elif nb_unknown:
2450 elif nb_unknown:
2448 msg = _(b"permanently delete %d unknown files?")
2451 msg = _(b"permanently delete %d unknown files?")
2449 msg %= nb_unknown
2452 msg %= nb_unknown
2450 elif nb_ignored:
2453 elif nb_ignored:
2451 msg = _(b"permanently delete %d ignored files?")
2454 msg = _(b"permanently delete %d ignored files?")
2452 msg %= nb_ignored
2455 msg %= nb_ignored
2453 elif removeemptydirs:
2456 elif removeemptydirs:
2454 dir_count = 0
2457 dir_count = 0
2455 for f in directories:
2458 for f in directories:
2456 if matcher(f) and not repo.wvfs.listdir(f):
2459 if matcher(f) and not repo.wvfs.listdir(f):
2457 dir_count += 1
2460 dir_count += 1
2458 if dir_count:
2461 if dir_count:
2459 msg = _(
2462 msg = _(
2460 b"permanently delete at least %d empty directories?"
2463 b"permanently delete at least %d empty directories?"
2461 )
2464 )
2462 msg %= dir_count
2465 msg %= dir_count
2463 if msg is None:
2466 if msg is None:
2464 return res
2467 return res
2465 else:
2468 else:
2466 msg += b" (yN)$$ &Yes $$ &No"
2469 msg += b" (yN)$$ &Yes $$ &No"
2467 if repo.ui.promptchoice(msg, default=1) == 1:
2470 if repo.ui.promptchoice(msg, default=1) == 1:
2468 raise error.CanceledError(_(b'removal cancelled'))
2471 raise error.CanceledError(_(b'removal cancelled'))
2469
2472
2470 if removefiles:
2473 if removefiles:
2471 for f in sorted(status.unknown + status.ignored):
2474 for f in sorted(status.unknown + status.ignored):
2472 if not noop:
2475 if not noop:
2473 repo.ui.note(_(b'removing file %s\n') % f)
2476 repo.ui.note(_(b'removing file %s\n') % f)
2474 remove(repo.wvfs.unlink, f)
2477 remove(repo.wvfs.unlink, f)
2475 res.append(f)
2478 res.append(f)
2476
2479
2477 if removeemptydirs:
2480 if removeemptydirs:
2478 for f in sorted(directories, reverse=True):
2481 for f in sorted(directories, reverse=True):
2479 if matcher(f) and not repo.wvfs.listdir(f):
2482 if matcher(f) and not repo.wvfs.listdir(f):
2480 if not noop:
2483 if not noop:
2481 repo.ui.note(_(b'removing directory %s\n') % f)
2484 repo.ui.note(_(b'removing directory %s\n') % f)
2482 remove(repo.wvfs.rmdir, f)
2485 remove(repo.wvfs.rmdir, f)
2483 res.append(f)
2486 res.append(f)
2484
2487
2485 return res
2488 return res
2486
2489
2487 finally:
2490 finally:
2488 matcher.traversedir = oldtraversedir
2491 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now