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