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