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