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