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