##// END OF EJS Templates
merge: refactor code to advise fsmonitor in separate function...
Pulkit Goyal -
r45727:796b63b0 default
parent child Browse files
Show More
@@ -1,2082 +1,2088 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 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import stat
11 import stat
12 import struct
12 import struct
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 addednodeid,
16 addednodeid,
17 modifiednodeid,
17 modifiednodeid,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 )
20 )
21 from .thirdparty import attr
21 from .thirdparty import attr
22 from . import (
22 from . import (
23 copies,
23 copies,
24 encoding,
24 encoding,
25 error,
25 error,
26 filemerge,
26 filemerge,
27 match as matchmod,
27 match as matchmod,
28 mergestate as mergestatemod,
28 mergestate as mergestatemod,
29 obsutil,
29 obsutil,
30 pathutil,
30 pathutil,
31 pycompat,
31 pycompat,
32 scmutil,
32 scmutil,
33 subrepoutil,
33 subrepoutil,
34 util,
34 util,
35 worker,
35 worker,
36 )
36 )
37
37
38 _pack = struct.pack
38 _pack = struct.pack
39 _unpack = struct.unpack
39 _unpack = struct.unpack
40
40
41
41
42 def _getcheckunknownconfig(repo, section, name):
42 def _getcheckunknownconfig(repo, section, name):
43 config = repo.ui.config(section, name)
43 config = repo.ui.config(section, name)
44 valid = [b'abort', b'ignore', b'warn']
44 valid = [b'abort', b'ignore', b'warn']
45 if config not in valid:
45 if config not in valid:
46 validstr = b', '.join([b"'" + v + b"'" for v in valid])
46 validstr = b', '.join([b"'" + v + b"'" for v in valid])
47 raise error.ConfigError(
47 raise error.ConfigError(
48 _(b"%s.%s not valid ('%s' is none of %s)")
48 _(b"%s.%s not valid ('%s' is none of %s)")
49 % (section, name, config, validstr)
49 % (section, name, config, validstr)
50 )
50 )
51 return config
51 return config
52
52
53
53
54 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
54 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
55 if wctx.isinmemory():
55 if wctx.isinmemory():
56 # Nothing to do in IMM because nothing in the "working copy" can be an
56 # Nothing to do in IMM because nothing in the "working copy" can be an
57 # unknown file.
57 # unknown file.
58 #
58 #
59 # Note that we should bail out here, not in ``_checkunknownfiles()``,
59 # Note that we should bail out here, not in ``_checkunknownfiles()``,
60 # because that function does other useful work.
60 # because that function does other useful work.
61 return False
61 return False
62
62
63 if f2 is None:
63 if f2 is None:
64 f2 = f
64 f2 = f
65 return (
65 return (
66 repo.wvfs.audit.check(f)
66 repo.wvfs.audit.check(f)
67 and repo.wvfs.isfileorlink(f)
67 and repo.wvfs.isfileorlink(f)
68 and repo.dirstate.normalize(f) not in repo.dirstate
68 and repo.dirstate.normalize(f) not in repo.dirstate
69 and mctx[f2].cmp(wctx[f])
69 and mctx[f2].cmp(wctx[f])
70 )
70 )
71
71
72
72
73 class _unknowndirschecker(object):
73 class _unknowndirschecker(object):
74 """
74 """
75 Look for any unknown files or directories that may have a path conflict
75 Look for any unknown files or directories that may have a path conflict
76 with a file. If any path prefix of the file exists as a file or link,
76 with a file. If any path prefix of the file exists as a file or link,
77 then it conflicts. If the file itself is a directory that contains any
77 then it conflicts. If the file itself is a directory that contains any
78 file that is not tracked, then it conflicts.
78 file that is not tracked, then it conflicts.
79
79
80 Returns the shortest path at which a conflict occurs, or None if there is
80 Returns the shortest path at which a conflict occurs, or None if there is
81 no conflict.
81 no conflict.
82 """
82 """
83
83
84 def __init__(self):
84 def __init__(self):
85 # A set of paths known to be good. This prevents repeated checking of
85 # A set of paths known to be good. This prevents repeated checking of
86 # dirs. It will be updated with any new dirs that are checked and found
86 # dirs. It will be updated with any new dirs that are checked and found
87 # to be safe.
87 # to be safe.
88 self._unknowndircache = set()
88 self._unknowndircache = set()
89
89
90 # A set of paths that are known to be absent. This prevents repeated
90 # A set of paths that are known to be absent. This prevents repeated
91 # checking of subdirectories that are known not to exist. It will be
91 # checking of subdirectories that are known not to exist. It will be
92 # updated with any new dirs that are checked and found to be absent.
92 # updated with any new dirs that are checked and found to be absent.
93 self._missingdircache = set()
93 self._missingdircache = set()
94
94
95 def __call__(self, repo, wctx, f):
95 def __call__(self, repo, wctx, f):
96 if wctx.isinmemory():
96 if wctx.isinmemory():
97 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
97 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
98 return False
98 return False
99
99
100 # Check for path prefixes that exist as unknown files.
100 # Check for path prefixes that exist as unknown files.
101 for p in reversed(list(pathutil.finddirs(f))):
101 for p in reversed(list(pathutil.finddirs(f))):
102 if p in self._missingdircache:
102 if p in self._missingdircache:
103 return
103 return
104 if p in self._unknowndircache:
104 if p in self._unknowndircache:
105 continue
105 continue
106 if repo.wvfs.audit.check(p):
106 if repo.wvfs.audit.check(p):
107 if (
107 if (
108 repo.wvfs.isfileorlink(p)
108 repo.wvfs.isfileorlink(p)
109 and repo.dirstate.normalize(p) not in repo.dirstate
109 and repo.dirstate.normalize(p) not in repo.dirstate
110 ):
110 ):
111 return p
111 return p
112 if not repo.wvfs.lexists(p):
112 if not repo.wvfs.lexists(p):
113 self._missingdircache.add(p)
113 self._missingdircache.add(p)
114 return
114 return
115 self._unknowndircache.add(p)
115 self._unknowndircache.add(p)
116
116
117 # Check if the file conflicts with a directory containing unknown files.
117 # Check if the file conflicts with a directory containing unknown files.
118 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
118 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
119 # Does the directory contain any files that are not in the dirstate?
119 # Does the directory contain any files that are not in the dirstate?
120 for p, dirs, files in repo.wvfs.walk(f):
120 for p, dirs, files in repo.wvfs.walk(f):
121 for fn in files:
121 for fn in files:
122 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
122 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
123 relf = repo.dirstate.normalize(relf, isknown=True)
123 relf = repo.dirstate.normalize(relf, isknown=True)
124 if relf not in repo.dirstate:
124 if relf not in repo.dirstate:
125 return f
125 return f
126 return None
126 return None
127
127
128
128
129 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
129 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
130 """
130 """
131 Considers any actions that care about the presence of conflicting unknown
131 Considers any actions that care about the presence of conflicting unknown
132 files. For some actions, the result is to abort; for others, it is to
132 files. For some actions, the result is to abort; for others, it is to
133 choose a different action.
133 choose a different action.
134 """
134 """
135 fileconflicts = set()
135 fileconflicts = set()
136 pathconflicts = set()
136 pathconflicts = set()
137 warnconflicts = set()
137 warnconflicts = set()
138 abortconflicts = set()
138 abortconflicts = set()
139 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
139 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
140 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
140 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
141 pathconfig = repo.ui.configbool(
141 pathconfig = repo.ui.configbool(
142 b'experimental', b'merge.checkpathconflicts'
142 b'experimental', b'merge.checkpathconflicts'
143 )
143 )
144 if not force:
144 if not force:
145
145
146 def collectconflicts(conflicts, config):
146 def collectconflicts(conflicts, config):
147 if config == b'abort':
147 if config == b'abort':
148 abortconflicts.update(conflicts)
148 abortconflicts.update(conflicts)
149 elif config == b'warn':
149 elif config == b'warn':
150 warnconflicts.update(conflicts)
150 warnconflicts.update(conflicts)
151
151
152 checkunknowndirs = _unknowndirschecker()
152 checkunknowndirs = _unknowndirschecker()
153 for f, (m, args, msg) in pycompat.iteritems(actions):
153 for f, (m, args, msg) in pycompat.iteritems(actions):
154 if m in (
154 if m in (
155 mergestatemod.ACTION_CREATED,
155 mergestatemod.ACTION_CREATED,
156 mergestatemod.ACTION_DELETED_CHANGED,
156 mergestatemod.ACTION_DELETED_CHANGED,
157 ):
157 ):
158 if _checkunknownfile(repo, wctx, mctx, f):
158 if _checkunknownfile(repo, wctx, mctx, f):
159 fileconflicts.add(f)
159 fileconflicts.add(f)
160 elif pathconfig and f not in wctx:
160 elif pathconfig and f not in wctx:
161 path = checkunknowndirs(repo, wctx, f)
161 path = checkunknowndirs(repo, wctx, f)
162 if path is not None:
162 if path is not None:
163 pathconflicts.add(path)
163 pathconflicts.add(path)
164 elif m == mergestatemod.ACTION_LOCAL_DIR_RENAME_GET:
164 elif m == mergestatemod.ACTION_LOCAL_DIR_RENAME_GET:
165 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
165 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
166 fileconflicts.add(f)
166 fileconflicts.add(f)
167
167
168 allconflicts = fileconflicts | pathconflicts
168 allconflicts = fileconflicts | pathconflicts
169 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
169 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
170 unknownconflicts = allconflicts - ignoredconflicts
170 unknownconflicts = allconflicts - ignoredconflicts
171 collectconflicts(ignoredconflicts, ignoredconfig)
171 collectconflicts(ignoredconflicts, ignoredconfig)
172 collectconflicts(unknownconflicts, unknownconfig)
172 collectconflicts(unknownconflicts, unknownconfig)
173 else:
173 else:
174 for f, (m, args, msg) in pycompat.iteritems(actions):
174 for f, (m, args, msg) in pycompat.iteritems(actions):
175 if m == mergestatemod.ACTION_CREATED_MERGE:
175 if m == mergestatemod.ACTION_CREATED_MERGE:
176 fl2, anc = args
176 fl2, anc = args
177 different = _checkunknownfile(repo, wctx, mctx, f)
177 different = _checkunknownfile(repo, wctx, mctx, f)
178 if repo.dirstate._ignore(f):
178 if repo.dirstate._ignore(f):
179 config = ignoredconfig
179 config = ignoredconfig
180 else:
180 else:
181 config = unknownconfig
181 config = unknownconfig
182
182
183 # The behavior when force is True is described by this table:
183 # The behavior when force is True is described by this table:
184 # config different mergeforce | action backup
184 # config different mergeforce | action backup
185 # * n * | get n
185 # * n * | get n
186 # * y y | merge -
186 # * y y | merge -
187 # abort y n | merge - (1)
187 # abort y n | merge - (1)
188 # warn y n | warn + get y
188 # warn y n | warn + get y
189 # ignore y n | get y
189 # ignore y n | get y
190 #
190 #
191 # (1) this is probably the wrong behavior here -- we should
191 # (1) this is probably the wrong behavior here -- we should
192 # probably abort, but some actions like rebases currently
192 # probably abort, but some actions like rebases currently
193 # don't like an abort happening in the middle of
193 # don't like an abort happening in the middle of
194 # merge.update.
194 # merge.update.
195 if not different:
195 if not different:
196 actions[f] = (
196 actions[f] = (
197 mergestatemod.ACTION_GET,
197 mergestatemod.ACTION_GET,
198 (fl2, False),
198 (fl2, False),
199 b'remote created',
199 b'remote created',
200 )
200 )
201 elif mergeforce or config == b'abort':
201 elif mergeforce or config == b'abort':
202 actions[f] = (
202 actions[f] = (
203 mergestatemod.ACTION_MERGE,
203 mergestatemod.ACTION_MERGE,
204 (f, f, None, False, anc),
204 (f, f, None, False, anc),
205 b'remote differs from untracked local',
205 b'remote differs from untracked local',
206 )
206 )
207 elif config == b'abort':
207 elif config == b'abort':
208 abortconflicts.add(f)
208 abortconflicts.add(f)
209 else:
209 else:
210 if config == b'warn':
210 if config == b'warn':
211 warnconflicts.add(f)
211 warnconflicts.add(f)
212 actions[f] = (
212 actions[f] = (
213 mergestatemod.ACTION_GET,
213 mergestatemod.ACTION_GET,
214 (fl2, True),
214 (fl2, True),
215 b'remote created',
215 b'remote created',
216 )
216 )
217
217
218 for f in sorted(abortconflicts):
218 for f in sorted(abortconflicts):
219 warn = repo.ui.warn
219 warn = repo.ui.warn
220 if f in pathconflicts:
220 if f in pathconflicts:
221 if repo.wvfs.isfileorlink(f):
221 if repo.wvfs.isfileorlink(f):
222 warn(_(b"%s: untracked file conflicts with directory\n") % f)
222 warn(_(b"%s: untracked file conflicts with directory\n") % f)
223 else:
223 else:
224 warn(_(b"%s: untracked directory conflicts with file\n") % f)
224 warn(_(b"%s: untracked directory conflicts with file\n") % f)
225 else:
225 else:
226 warn(_(b"%s: untracked file differs\n") % f)
226 warn(_(b"%s: untracked file differs\n") % f)
227 if abortconflicts:
227 if abortconflicts:
228 raise error.Abort(
228 raise error.Abort(
229 _(
229 _(
230 b"untracked files in working directory "
230 b"untracked files in working directory "
231 b"differ from files in requested revision"
231 b"differ from files in requested revision"
232 )
232 )
233 )
233 )
234
234
235 for f in sorted(warnconflicts):
235 for f in sorted(warnconflicts):
236 if repo.wvfs.isfileorlink(f):
236 if repo.wvfs.isfileorlink(f):
237 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
237 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
238 else:
238 else:
239 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
239 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
240
240
241 for f, (m, args, msg) in pycompat.iteritems(actions):
241 for f, (m, args, msg) in pycompat.iteritems(actions):
242 if m == mergestatemod.ACTION_CREATED:
242 if m == mergestatemod.ACTION_CREATED:
243 backup = (
243 backup = (
244 f in fileconflicts
244 f in fileconflicts
245 or f in pathconflicts
245 or f in pathconflicts
246 or any(p in pathconflicts for p in pathutil.finddirs(f))
246 or any(p in pathconflicts for p in pathutil.finddirs(f))
247 )
247 )
248 (flags,) = args
248 (flags,) = args
249 actions[f] = (mergestatemod.ACTION_GET, (flags, backup), msg)
249 actions[f] = (mergestatemod.ACTION_GET, (flags, backup), msg)
250
250
251
251
252 def _forgetremoved(wctx, mctx, branchmerge):
252 def _forgetremoved(wctx, mctx, branchmerge):
253 """
253 """
254 Forget removed files
254 Forget removed files
255
255
256 If we're jumping between revisions (as opposed to merging), and if
256 If we're jumping between revisions (as opposed to merging), and if
257 neither the working directory nor the target rev has the file,
257 neither the working directory nor the target rev has the file,
258 then we need to remove it from the dirstate, to prevent the
258 then we need to remove it from the dirstate, to prevent the
259 dirstate from listing the file when it is no longer in the
259 dirstate from listing the file when it is no longer in the
260 manifest.
260 manifest.
261
261
262 If we're merging, and the other revision has removed a file
262 If we're merging, and the other revision has removed a file
263 that is not present in the working directory, we need to mark it
263 that is not present in the working directory, we need to mark it
264 as removed.
264 as removed.
265 """
265 """
266
266
267 actions = {}
267 actions = {}
268 m = mergestatemod.ACTION_FORGET
268 m = mergestatemod.ACTION_FORGET
269 if branchmerge:
269 if branchmerge:
270 m = mergestatemod.ACTION_REMOVE
270 m = mergestatemod.ACTION_REMOVE
271 for f in wctx.deleted():
271 for f in wctx.deleted():
272 if f not in mctx:
272 if f not in mctx:
273 actions[f] = m, None, b"forget deleted"
273 actions[f] = m, None, b"forget deleted"
274
274
275 if not branchmerge:
275 if not branchmerge:
276 for f in wctx.removed():
276 for f in wctx.removed():
277 if f not in mctx:
277 if f not in mctx:
278 actions[f] = (
278 actions[f] = (
279 mergestatemod.ACTION_FORGET,
279 mergestatemod.ACTION_FORGET,
280 None,
280 None,
281 b"forget removed",
281 b"forget removed",
282 )
282 )
283
283
284 return actions
284 return actions
285
285
286
286
287 def _checkcollision(repo, wmf, actions):
287 def _checkcollision(repo, wmf, actions):
288 """
288 """
289 Check for case-folding collisions.
289 Check for case-folding collisions.
290 """
290 """
291 # If the repo is narrowed, filter out files outside the narrowspec.
291 # If the repo is narrowed, filter out files outside the narrowspec.
292 narrowmatch = repo.narrowmatch()
292 narrowmatch = repo.narrowmatch()
293 if not narrowmatch.always():
293 if not narrowmatch.always():
294 pmmf = set(wmf.walk(narrowmatch))
294 pmmf = set(wmf.walk(narrowmatch))
295 if actions:
295 if actions:
296 narrowactions = {}
296 narrowactions = {}
297 for m, actionsfortype in pycompat.iteritems(actions):
297 for m, actionsfortype in pycompat.iteritems(actions):
298 narrowactions[m] = []
298 narrowactions[m] = []
299 for (f, args, msg) in actionsfortype:
299 for (f, args, msg) in actionsfortype:
300 if narrowmatch(f):
300 if narrowmatch(f):
301 narrowactions[m].append((f, args, msg))
301 narrowactions[m].append((f, args, msg))
302 actions = narrowactions
302 actions = narrowactions
303 else:
303 else:
304 # build provisional merged manifest up
304 # build provisional merged manifest up
305 pmmf = set(wmf)
305 pmmf = set(wmf)
306
306
307 if actions:
307 if actions:
308 # KEEP and EXEC are no-op
308 # KEEP and EXEC are no-op
309 for m in (
309 for m in (
310 mergestatemod.ACTION_ADD,
310 mergestatemod.ACTION_ADD,
311 mergestatemod.ACTION_ADD_MODIFIED,
311 mergestatemod.ACTION_ADD_MODIFIED,
312 mergestatemod.ACTION_FORGET,
312 mergestatemod.ACTION_FORGET,
313 mergestatemod.ACTION_GET,
313 mergestatemod.ACTION_GET,
314 mergestatemod.ACTION_CHANGED_DELETED,
314 mergestatemod.ACTION_CHANGED_DELETED,
315 mergestatemod.ACTION_DELETED_CHANGED,
315 mergestatemod.ACTION_DELETED_CHANGED,
316 ):
316 ):
317 for f, args, msg in actions[m]:
317 for f, args, msg in actions[m]:
318 pmmf.add(f)
318 pmmf.add(f)
319 for f, args, msg in actions[mergestatemod.ACTION_REMOVE]:
319 for f, args, msg in actions[mergestatemod.ACTION_REMOVE]:
320 pmmf.discard(f)
320 pmmf.discard(f)
321 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
321 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
322 f2, flags = args
322 f2, flags = args
323 pmmf.discard(f2)
323 pmmf.discard(f2)
324 pmmf.add(f)
324 pmmf.add(f)
325 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
325 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
326 pmmf.add(f)
326 pmmf.add(f)
327 for f, args, msg in actions[mergestatemod.ACTION_MERGE]:
327 for f, args, msg in actions[mergestatemod.ACTION_MERGE]:
328 f1, f2, fa, move, anc = args
328 f1, f2, fa, move, anc = args
329 if move:
329 if move:
330 pmmf.discard(f1)
330 pmmf.discard(f1)
331 pmmf.add(f)
331 pmmf.add(f)
332
332
333 # check case-folding collision in provisional merged manifest
333 # check case-folding collision in provisional merged manifest
334 foldmap = {}
334 foldmap = {}
335 for f in pmmf:
335 for f in pmmf:
336 fold = util.normcase(f)
336 fold = util.normcase(f)
337 if fold in foldmap:
337 if fold in foldmap:
338 raise error.Abort(
338 raise error.Abort(
339 _(b"case-folding collision between %s and %s")
339 _(b"case-folding collision between %s and %s")
340 % (f, foldmap[fold])
340 % (f, foldmap[fold])
341 )
341 )
342 foldmap[fold] = f
342 foldmap[fold] = f
343
343
344 # check case-folding of directories
344 # check case-folding of directories
345 foldprefix = unfoldprefix = lastfull = b''
345 foldprefix = unfoldprefix = lastfull = b''
346 for fold, f in sorted(foldmap.items()):
346 for fold, f in sorted(foldmap.items()):
347 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
347 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
348 # the folded prefix matches but actual casing is different
348 # the folded prefix matches but actual casing is different
349 raise error.Abort(
349 raise error.Abort(
350 _(b"case-folding collision between %s and directory of %s")
350 _(b"case-folding collision between %s and directory of %s")
351 % (lastfull, f)
351 % (lastfull, f)
352 )
352 )
353 foldprefix = fold + b'/'
353 foldprefix = fold + b'/'
354 unfoldprefix = f + b'/'
354 unfoldprefix = f + b'/'
355 lastfull = f
355 lastfull = f
356
356
357
357
358 def driverpreprocess(repo, ms, wctx, labels=None):
358 def driverpreprocess(repo, ms, wctx, labels=None):
359 """run the preprocess step of the merge driver, if any
359 """run the preprocess step of the merge driver, if any
360
360
361 This is currently not implemented -- it's an extension point."""
361 This is currently not implemented -- it's an extension point."""
362 return True
362 return True
363
363
364
364
365 def driverconclude(repo, ms, wctx, labels=None):
365 def driverconclude(repo, ms, wctx, labels=None):
366 """run the conclude step of the merge driver, if any
366 """run the conclude step of the merge driver, if any
367
367
368 This is currently not implemented -- it's an extension point."""
368 This is currently not implemented -- it's an extension point."""
369 return True
369 return True
370
370
371
371
372 def _filesindirs(repo, manifest, dirs):
372 def _filesindirs(repo, manifest, dirs):
373 """
373 """
374 Generator that yields pairs of all the files in the manifest that are found
374 Generator that yields pairs of all the files in the manifest that are found
375 inside the directories listed in dirs, and which directory they are found
375 inside the directories listed in dirs, and which directory they are found
376 in.
376 in.
377 """
377 """
378 for f in manifest:
378 for f in manifest:
379 for p in pathutil.finddirs(f):
379 for p in pathutil.finddirs(f):
380 if p in dirs:
380 if p in dirs:
381 yield f, p
381 yield f, p
382 break
382 break
383
383
384
384
385 def checkpathconflicts(repo, wctx, mctx, actions):
385 def checkpathconflicts(repo, wctx, mctx, actions):
386 """
386 """
387 Check if any actions introduce path conflicts in the repository, updating
387 Check if any actions introduce path conflicts in the repository, updating
388 actions to record or handle the path conflict accordingly.
388 actions to record or handle the path conflict accordingly.
389 """
389 """
390 mf = wctx.manifest()
390 mf = wctx.manifest()
391
391
392 # The set of local files that conflict with a remote directory.
392 # The set of local files that conflict with a remote directory.
393 localconflicts = set()
393 localconflicts = set()
394
394
395 # The set of directories that conflict with a remote file, and so may cause
395 # The set of directories that conflict with a remote file, and so may cause
396 # conflicts if they still contain any files after the merge.
396 # conflicts if they still contain any files after the merge.
397 remoteconflicts = set()
397 remoteconflicts = set()
398
398
399 # The set of directories that appear as both a file and a directory in the
399 # The set of directories that appear as both a file and a directory in the
400 # remote manifest. These indicate an invalid remote manifest, which
400 # remote manifest. These indicate an invalid remote manifest, which
401 # can't be updated to cleanly.
401 # can't be updated to cleanly.
402 invalidconflicts = set()
402 invalidconflicts = set()
403
403
404 # The set of directories that contain files that are being created.
404 # The set of directories that contain files that are being created.
405 createdfiledirs = set()
405 createdfiledirs = set()
406
406
407 # The set of files deleted by all the actions.
407 # The set of files deleted by all the actions.
408 deletedfiles = set()
408 deletedfiles = set()
409
409
410 for f, (m, args, msg) in actions.items():
410 for f, (m, args, msg) in actions.items():
411 if m in (
411 if m in (
412 mergestatemod.ACTION_CREATED,
412 mergestatemod.ACTION_CREATED,
413 mergestatemod.ACTION_DELETED_CHANGED,
413 mergestatemod.ACTION_DELETED_CHANGED,
414 mergestatemod.ACTION_MERGE,
414 mergestatemod.ACTION_MERGE,
415 mergestatemod.ACTION_CREATED_MERGE,
415 mergestatemod.ACTION_CREATED_MERGE,
416 ):
416 ):
417 # This action may create a new local file.
417 # This action may create a new local file.
418 createdfiledirs.update(pathutil.finddirs(f))
418 createdfiledirs.update(pathutil.finddirs(f))
419 if mf.hasdir(f):
419 if mf.hasdir(f):
420 # The file aliases a local directory. This might be ok if all
420 # The file aliases a local directory. This might be ok if all
421 # the files in the local directory are being deleted. This
421 # the files in the local directory are being deleted. This
422 # will be checked once we know what all the deleted files are.
422 # will be checked once we know what all the deleted files are.
423 remoteconflicts.add(f)
423 remoteconflicts.add(f)
424 # Track the names of all deleted files.
424 # Track the names of all deleted files.
425 if m == mergestatemod.ACTION_REMOVE:
425 if m == mergestatemod.ACTION_REMOVE:
426 deletedfiles.add(f)
426 deletedfiles.add(f)
427 if m == mergestatemod.ACTION_MERGE:
427 if m == mergestatemod.ACTION_MERGE:
428 f1, f2, fa, move, anc = args
428 f1, f2, fa, move, anc = args
429 if move:
429 if move:
430 deletedfiles.add(f1)
430 deletedfiles.add(f1)
431 if m == mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL:
431 if m == mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL:
432 f2, flags = args
432 f2, flags = args
433 deletedfiles.add(f2)
433 deletedfiles.add(f2)
434
434
435 # Check all directories that contain created files for path conflicts.
435 # Check all directories that contain created files for path conflicts.
436 for p in createdfiledirs:
436 for p in createdfiledirs:
437 if p in mf:
437 if p in mf:
438 if p in mctx:
438 if p in mctx:
439 # A file is in a directory which aliases both a local
439 # A file is in a directory which aliases both a local
440 # and a remote file. This is an internal inconsistency
440 # and a remote file. This is an internal inconsistency
441 # within the remote manifest.
441 # within the remote manifest.
442 invalidconflicts.add(p)
442 invalidconflicts.add(p)
443 else:
443 else:
444 # A file is in a directory which aliases a local file.
444 # A file is in a directory which aliases a local file.
445 # We will need to rename the local file.
445 # We will need to rename the local file.
446 localconflicts.add(p)
446 localconflicts.add(p)
447 if p in actions and actions[p][0] in (
447 if p in actions and actions[p][0] in (
448 mergestatemod.ACTION_CREATED,
448 mergestatemod.ACTION_CREATED,
449 mergestatemod.ACTION_DELETED_CHANGED,
449 mergestatemod.ACTION_DELETED_CHANGED,
450 mergestatemod.ACTION_MERGE,
450 mergestatemod.ACTION_MERGE,
451 mergestatemod.ACTION_CREATED_MERGE,
451 mergestatemod.ACTION_CREATED_MERGE,
452 ):
452 ):
453 # The file is in a directory which aliases a remote file.
453 # The file is in a directory which aliases a remote file.
454 # This is an internal inconsistency within the remote
454 # This is an internal inconsistency within the remote
455 # manifest.
455 # manifest.
456 invalidconflicts.add(p)
456 invalidconflicts.add(p)
457
457
458 # Rename all local conflicting files that have not been deleted.
458 # Rename all local conflicting files that have not been deleted.
459 for p in localconflicts:
459 for p in localconflicts:
460 if p not in deletedfiles:
460 if p not in deletedfiles:
461 ctxname = bytes(wctx).rstrip(b'+')
461 ctxname = bytes(wctx).rstrip(b'+')
462 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
462 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
463 porig = wctx[p].copysource() or p
463 porig = wctx[p].copysource() or p
464 actions[pnew] = (
464 actions[pnew] = (
465 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
465 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
466 (p, porig),
466 (p, porig),
467 b'local path conflict',
467 b'local path conflict',
468 )
468 )
469 actions[p] = (
469 actions[p] = (
470 mergestatemod.ACTION_PATH_CONFLICT,
470 mergestatemod.ACTION_PATH_CONFLICT,
471 (pnew, b'l'),
471 (pnew, b'l'),
472 b'path conflict',
472 b'path conflict',
473 )
473 )
474
474
475 if remoteconflicts:
475 if remoteconflicts:
476 # Check if all files in the conflicting directories have been removed.
476 # Check if all files in the conflicting directories have been removed.
477 ctxname = bytes(mctx).rstrip(b'+')
477 ctxname = bytes(mctx).rstrip(b'+')
478 for f, p in _filesindirs(repo, mf, remoteconflicts):
478 for f, p in _filesindirs(repo, mf, remoteconflicts):
479 if f not in deletedfiles:
479 if f not in deletedfiles:
480 m, args, msg = actions[p]
480 m, args, msg = actions[p]
481 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
481 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
482 if m in (
482 if m in (
483 mergestatemod.ACTION_DELETED_CHANGED,
483 mergestatemod.ACTION_DELETED_CHANGED,
484 mergestatemod.ACTION_MERGE,
484 mergestatemod.ACTION_MERGE,
485 ):
485 ):
486 # Action was merge, just update target.
486 # Action was merge, just update target.
487 actions[pnew] = (m, args, msg)
487 actions[pnew] = (m, args, msg)
488 else:
488 else:
489 # Action was create, change to renamed get action.
489 # Action was create, change to renamed get action.
490 fl = args[0]
490 fl = args[0]
491 actions[pnew] = (
491 actions[pnew] = (
492 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
492 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
493 (p, fl),
493 (p, fl),
494 b'remote path conflict',
494 b'remote path conflict',
495 )
495 )
496 actions[p] = (
496 actions[p] = (
497 mergestatemod.ACTION_PATH_CONFLICT,
497 mergestatemod.ACTION_PATH_CONFLICT,
498 (pnew, mergestatemod.ACTION_REMOVE),
498 (pnew, mergestatemod.ACTION_REMOVE),
499 b'path conflict',
499 b'path conflict',
500 )
500 )
501 remoteconflicts.remove(p)
501 remoteconflicts.remove(p)
502 break
502 break
503
503
504 if invalidconflicts:
504 if invalidconflicts:
505 for p in invalidconflicts:
505 for p in invalidconflicts:
506 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
506 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
507 raise error.Abort(_(b"destination manifest contains path conflicts"))
507 raise error.Abort(_(b"destination manifest contains path conflicts"))
508
508
509
509
510 def _filternarrowactions(narrowmatch, branchmerge, actions):
510 def _filternarrowactions(narrowmatch, branchmerge, actions):
511 """
511 """
512 Filters out actions that can ignored because the repo is narrowed.
512 Filters out actions that can ignored because the repo is narrowed.
513
513
514 Raise an exception if the merge cannot be completed because the repo is
514 Raise an exception if the merge cannot be completed because the repo is
515 narrowed.
515 narrowed.
516 """
516 """
517 nooptypes = {b'k'} # TODO: handle with nonconflicttypes
517 nooptypes = {b'k'} # TODO: handle with nonconflicttypes
518 nonconflicttypes = set(b'a am c cm f g gs r e'.split())
518 nonconflicttypes = set(b'a am c cm f g gs r e'.split())
519 # We mutate the items in the dict during iteration, so iterate
519 # We mutate the items in the dict during iteration, so iterate
520 # over a copy.
520 # over a copy.
521 for f, action in list(actions.items()):
521 for f, action in list(actions.items()):
522 if narrowmatch(f):
522 if narrowmatch(f):
523 pass
523 pass
524 elif not branchmerge:
524 elif not branchmerge:
525 del actions[f] # just updating, ignore changes outside clone
525 del actions[f] # just updating, ignore changes outside clone
526 elif action[0] in nooptypes:
526 elif action[0] in nooptypes:
527 del actions[f] # merge does not affect file
527 del actions[f] # merge does not affect file
528 elif action[0] in nonconflicttypes:
528 elif action[0] in nonconflicttypes:
529 raise error.Abort(
529 raise error.Abort(
530 _(
530 _(
531 b'merge affects file \'%s\' outside narrow, '
531 b'merge affects file \'%s\' outside narrow, '
532 b'which is not yet supported'
532 b'which is not yet supported'
533 )
533 )
534 % f,
534 % f,
535 hint=_(b'merging in the other direction may work'),
535 hint=_(b'merging in the other direction may work'),
536 )
536 )
537 else:
537 else:
538 raise error.Abort(
538 raise error.Abort(
539 _(b'conflict in file \'%s\' is outside narrow clone') % f
539 _(b'conflict in file \'%s\' is outside narrow clone') % f
540 )
540 )
541
541
542
542
543 def manifestmerge(
543 def manifestmerge(
544 repo,
544 repo,
545 wctx,
545 wctx,
546 p2,
546 p2,
547 pa,
547 pa,
548 branchmerge,
548 branchmerge,
549 force,
549 force,
550 matcher,
550 matcher,
551 acceptremote,
551 acceptremote,
552 followcopies,
552 followcopies,
553 forcefulldiff=False,
553 forcefulldiff=False,
554 ):
554 ):
555 """
555 """
556 Merge wctx and p2 with ancestor pa and generate merge action list
556 Merge wctx and p2 with ancestor pa and generate merge action list
557
557
558 branchmerge and force are as passed in to update
558 branchmerge and force are as passed in to update
559 matcher = matcher to filter file lists
559 matcher = matcher to filter file lists
560 acceptremote = accept the incoming changes without prompting
560 acceptremote = accept the incoming changes without prompting
561 """
561 """
562 if matcher is not None and matcher.always():
562 if matcher is not None and matcher.always():
563 matcher = None
563 matcher = None
564
564
565 # manifests fetched in order are going to be faster, so prime the caches
565 # manifests fetched in order are going to be faster, so prime the caches
566 [
566 [
567 x.manifest()
567 x.manifest()
568 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
568 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
569 ]
569 ]
570
570
571 branch_copies1 = copies.branch_copies()
571 branch_copies1 = copies.branch_copies()
572 branch_copies2 = copies.branch_copies()
572 branch_copies2 = copies.branch_copies()
573 diverge = {}
573 diverge = {}
574 if followcopies:
574 if followcopies:
575 branch_copies1, branch_copies2, diverge = copies.mergecopies(
575 branch_copies1, branch_copies2, diverge = copies.mergecopies(
576 repo, wctx, p2, pa
576 repo, wctx, p2, pa
577 )
577 )
578
578
579 boolbm = pycompat.bytestr(bool(branchmerge))
579 boolbm = pycompat.bytestr(bool(branchmerge))
580 boolf = pycompat.bytestr(bool(force))
580 boolf = pycompat.bytestr(bool(force))
581 boolm = pycompat.bytestr(bool(matcher))
581 boolm = pycompat.bytestr(bool(matcher))
582 repo.ui.note(_(b"resolving manifests\n"))
582 repo.ui.note(_(b"resolving manifests\n"))
583 repo.ui.debug(
583 repo.ui.debug(
584 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
584 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
585 )
585 )
586 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
586 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
587
587
588 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
588 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
589 copied1 = set(branch_copies1.copy.values())
589 copied1 = set(branch_copies1.copy.values())
590 copied1.update(branch_copies1.movewithdir.values())
590 copied1.update(branch_copies1.movewithdir.values())
591 copied2 = set(branch_copies2.copy.values())
591 copied2 = set(branch_copies2.copy.values())
592 copied2.update(branch_copies2.movewithdir.values())
592 copied2.update(branch_copies2.movewithdir.values())
593
593
594 if b'.hgsubstate' in m1 and wctx.rev() is None:
594 if b'.hgsubstate' in m1 and wctx.rev() is None:
595 # Check whether sub state is modified, and overwrite the manifest
595 # Check whether sub state is modified, and overwrite the manifest
596 # to flag the change. If wctx is a committed revision, we shouldn't
596 # to flag the change. If wctx is a committed revision, we shouldn't
597 # care for the dirty state of the working directory.
597 # care for the dirty state of the working directory.
598 if any(wctx.sub(s).dirty() for s in wctx.substate):
598 if any(wctx.sub(s).dirty() for s in wctx.substate):
599 m1[b'.hgsubstate'] = modifiednodeid
599 m1[b'.hgsubstate'] = modifiednodeid
600
600
601 # Don't use m2-vs-ma optimization if:
601 # Don't use m2-vs-ma optimization if:
602 # - ma is the same as m1 or m2, which we're just going to diff again later
602 # - ma is the same as m1 or m2, which we're just going to diff again later
603 # - The caller specifically asks for a full diff, which is useful during bid
603 # - The caller specifically asks for a full diff, which is useful during bid
604 # merge.
604 # merge.
605 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
605 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
606 # Identify which files are relevant to the merge, so we can limit the
606 # Identify which files are relevant to the merge, so we can limit the
607 # total m1-vs-m2 diff to just those files. This has significant
607 # total m1-vs-m2 diff to just those files. This has significant
608 # performance benefits in large repositories.
608 # performance benefits in large repositories.
609 relevantfiles = set(ma.diff(m2).keys())
609 relevantfiles = set(ma.diff(m2).keys())
610
610
611 # For copied and moved files, we need to add the source file too.
611 # For copied and moved files, we need to add the source file too.
612 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
612 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
613 if copyvalue in relevantfiles:
613 if copyvalue in relevantfiles:
614 relevantfiles.add(copykey)
614 relevantfiles.add(copykey)
615 for movedirkey in branch_copies1.movewithdir:
615 for movedirkey in branch_copies1.movewithdir:
616 relevantfiles.add(movedirkey)
616 relevantfiles.add(movedirkey)
617 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
617 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
618 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
618 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
619
619
620 diff = m1.diff(m2, match=matcher)
620 diff = m1.diff(m2, match=matcher)
621
621
622 actions = {}
622 actions = {}
623 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
623 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
624 if n1 and n2: # file exists on both local and remote side
624 if n1 and n2: # file exists on both local and remote side
625 if f not in ma:
625 if f not in ma:
626 # TODO: what if they're renamed from different sources?
626 # TODO: what if they're renamed from different sources?
627 fa = branch_copies1.copy.get(
627 fa = branch_copies1.copy.get(
628 f, None
628 f, None
629 ) or branch_copies2.copy.get(f, None)
629 ) or branch_copies2.copy.get(f, None)
630 if fa is not None:
630 if fa is not None:
631 actions[f] = (
631 actions[f] = (
632 mergestatemod.ACTION_MERGE,
632 mergestatemod.ACTION_MERGE,
633 (f, f, fa, False, pa.node()),
633 (f, f, fa, False, pa.node()),
634 b'both renamed from %s' % fa,
634 b'both renamed from %s' % fa,
635 )
635 )
636 else:
636 else:
637 actions[f] = (
637 actions[f] = (
638 mergestatemod.ACTION_MERGE,
638 mergestatemod.ACTION_MERGE,
639 (f, f, None, False, pa.node()),
639 (f, f, None, False, pa.node()),
640 b'both created',
640 b'both created',
641 )
641 )
642 else:
642 else:
643 a = ma[f]
643 a = ma[f]
644 fla = ma.flags(f)
644 fla = ma.flags(f)
645 nol = b'l' not in fl1 + fl2 + fla
645 nol = b'l' not in fl1 + fl2 + fla
646 if n2 == a and fl2 == fla:
646 if n2 == a and fl2 == fla:
647 actions[f] = (
647 actions[f] = (
648 mergestatemod.ACTION_KEEP,
648 mergestatemod.ACTION_KEEP,
649 (),
649 (),
650 b'remote unchanged',
650 b'remote unchanged',
651 )
651 )
652 elif n1 == a and fl1 == fla: # local unchanged - use remote
652 elif n1 == a and fl1 == fla: # local unchanged - use remote
653 if n1 == n2: # optimization: keep local content
653 if n1 == n2: # optimization: keep local content
654 actions[f] = (
654 actions[f] = (
655 mergestatemod.ACTION_EXEC,
655 mergestatemod.ACTION_EXEC,
656 (fl2,),
656 (fl2,),
657 b'update permissions',
657 b'update permissions',
658 )
658 )
659 else:
659 else:
660 actions[f] = (
660 actions[f] = (
661 mergestatemod.ACTION_GET_OTHER_AND_STORE
661 mergestatemod.ACTION_GET_OTHER_AND_STORE
662 if branchmerge
662 if branchmerge
663 else mergestatemod.ACTION_GET,
663 else mergestatemod.ACTION_GET,
664 (fl2, False),
664 (fl2, False),
665 b'remote is newer',
665 b'remote is newer',
666 )
666 )
667 elif nol and n2 == a: # remote only changed 'x'
667 elif nol and n2 == a: # remote only changed 'x'
668 actions[f] = (
668 actions[f] = (
669 mergestatemod.ACTION_EXEC,
669 mergestatemod.ACTION_EXEC,
670 (fl2,),
670 (fl2,),
671 b'update permissions',
671 b'update permissions',
672 )
672 )
673 elif nol and n1 == a: # local only changed 'x'
673 elif nol and n1 == a: # local only changed 'x'
674 actions[f] = (
674 actions[f] = (
675 mergestatemod.ACTION_GET_OTHER_AND_STORE
675 mergestatemod.ACTION_GET_OTHER_AND_STORE
676 if branchmerge
676 if branchmerge
677 else mergestatemod.ACTION_GET,
677 else mergestatemod.ACTION_GET,
678 (fl1, False),
678 (fl1, False),
679 b'remote is newer',
679 b'remote is newer',
680 )
680 )
681 else: # both changed something
681 else: # both changed something
682 actions[f] = (
682 actions[f] = (
683 mergestatemod.ACTION_MERGE,
683 mergestatemod.ACTION_MERGE,
684 (f, f, f, False, pa.node()),
684 (f, f, f, False, pa.node()),
685 b'versions differ',
685 b'versions differ',
686 )
686 )
687 elif n1: # file exists only on local side
687 elif n1: # file exists only on local side
688 if f in copied2:
688 if f in copied2:
689 pass # we'll deal with it on m2 side
689 pass # we'll deal with it on m2 side
690 elif (
690 elif (
691 f in branch_copies1.movewithdir
691 f in branch_copies1.movewithdir
692 ): # directory rename, move local
692 ): # directory rename, move local
693 f2 = branch_copies1.movewithdir[f]
693 f2 = branch_copies1.movewithdir[f]
694 if f2 in m2:
694 if f2 in m2:
695 actions[f2] = (
695 actions[f2] = (
696 mergestatemod.ACTION_MERGE,
696 mergestatemod.ACTION_MERGE,
697 (f, f2, None, True, pa.node()),
697 (f, f2, None, True, pa.node()),
698 b'remote directory rename, both created',
698 b'remote directory rename, both created',
699 )
699 )
700 else:
700 else:
701 actions[f2] = (
701 actions[f2] = (
702 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
702 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
703 (f, fl1),
703 (f, fl1),
704 b'remote directory rename - move from %s' % f,
704 b'remote directory rename - move from %s' % f,
705 )
705 )
706 elif f in branch_copies1.copy:
706 elif f in branch_copies1.copy:
707 f2 = branch_copies1.copy[f]
707 f2 = branch_copies1.copy[f]
708 actions[f] = (
708 actions[f] = (
709 mergestatemod.ACTION_MERGE,
709 mergestatemod.ACTION_MERGE,
710 (f, f2, f2, False, pa.node()),
710 (f, f2, f2, False, pa.node()),
711 b'local copied/moved from %s' % f2,
711 b'local copied/moved from %s' % f2,
712 )
712 )
713 elif f in ma: # clean, a different, no remote
713 elif f in ma: # clean, a different, no remote
714 if n1 != ma[f]:
714 if n1 != ma[f]:
715 if acceptremote:
715 if acceptremote:
716 actions[f] = (
716 actions[f] = (
717 mergestatemod.ACTION_REMOVE,
717 mergestatemod.ACTION_REMOVE,
718 None,
718 None,
719 b'remote delete',
719 b'remote delete',
720 )
720 )
721 else:
721 else:
722 actions[f] = (
722 actions[f] = (
723 mergestatemod.ACTION_CHANGED_DELETED,
723 mergestatemod.ACTION_CHANGED_DELETED,
724 (f, None, f, False, pa.node()),
724 (f, None, f, False, pa.node()),
725 b'prompt changed/deleted',
725 b'prompt changed/deleted',
726 )
726 )
727 elif n1 == addednodeid:
727 elif n1 == addednodeid:
728 # This file was locally added. We should forget it instead of
728 # This file was locally added. We should forget it instead of
729 # deleting it.
729 # deleting it.
730 actions[f] = (
730 actions[f] = (
731 mergestatemod.ACTION_FORGET,
731 mergestatemod.ACTION_FORGET,
732 None,
732 None,
733 b'remote deleted',
733 b'remote deleted',
734 )
734 )
735 else:
735 else:
736 actions[f] = (
736 actions[f] = (
737 mergestatemod.ACTION_REMOVE,
737 mergestatemod.ACTION_REMOVE,
738 None,
738 None,
739 b'other deleted',
739 b'other deleted',
740 )
740 )
741 elif n2: # file exists only on remote side
741 elif n2: # file exists only on remote side
742 if f in copied1:
742 if f in copied1:
743 pass # we'll deal with it on m1 side
743 pass # we'll deal with it on m1 side
744 elif f in branch_copies2.movewithdir:
744 elif f in branch_copies2.movewithdir:
745 f2 = branch_copies2.movewithdir[f]
745 f2 = branch_copies2.movewithdir[f]
746 if f2 in m1:
746 if f2 in m1:
747 actions[f2] = (
747 actions[f2] = (
748 mergestatemod.ACTION_MERGE,
748 mergestatemod.ACTION_MERGE,
749 (f2, f, None, False, pa.node()),
749 (f2, f, None, False, pa.node()),
750 b'local directory rename, both created',
750 b'local directory rename, both created',
751 )
751 )
752 else:
752 else:
753 actions[f2] = (
753 actions[f2] = (
754 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
754 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
755 (f, fl2),
755 (f, fl2),
756 b'local directory rename - get from %s' % f,
756 b'local directory rename - get from %s' % f,
757 )
757 )
758 elif f in branch_copies2.copy:
758 elif f in branch_copies2.copy:
759 f2 = branch_copies2.copy[f]
759 f2 = branch_copies2.copy[f]
760 if f2 in m2:
760 if f2 in m2:
761 actions[f] = (
761 actions[f] = (
762 mergestatemod.ACTION_MERGE,
762 mergestatemod.ACTION_MERGE,
763 (f2, f, f2, False, pa.node()),
763 (f2, f, f2, False, pa.node()),
764 b'remote copied from %s' % f2,
764 b'remote copied from %s' % f2,
765 )
765 )
766 else:
766 else:
767 actions[f] = (
767 actions[f] = (
768 mergestatemod.ACTION_MERGE,
768 mergestatemod.ACTION_MERGE,
769 (f2, f, f2, True, pa.node()),
769 (f2, f, f2, True, pa.node()),
770 b'remote moved from %s' % f2,
770 b'remote moved from %s' % f2,
771 )
771 )
772 elif f not in ma:
772 elif f not in ma:
773 # local unknown, remote created: the logic is described by the
773 # local unknown, remote created: the logic is described by the
774 # following table:
774 # following table:
775 #
775 #
776 # force branchmerge different | action
776 # force branchmerge different | action
777 # n * * | create
777 # n * * | create
778 # y n * | create
778 # y n * | create
779 # y y n | create
779 # y y n | create
780 # y y y | merge
780 # y y y | merge
781 #
781 #
782 # Checking whether the files are different is expensive, so we
782 # Checking whether the files are different is expensive, so we
783 # don't do that when we can avoid it.
783 # don't do that when we can avoid it.
784 if not force:
784 if not force:
785 actions[f] = (
785 actions[f] = (
786 mergestatemod.ACTION_CREATED,
786 mergestatemod.ACTION_CREATED,
787 (fl2,),
787 (fl2,),
788 b'remote created',
788 b'remote created',
789 )
789 )
790 elif not branchmerge:
790 elif not branchmerge:
791 actions[f] = (
791 actions[f] = (
792 mergestatemod.ACTION_CREATED,
792 mergestatemod.ACTION_CREATED,
793 (fl2,),
793 (fl2,),
794 b'remote created',
794 b'remote created',
795 )
795 )
796 else:
796 else:
797 actions[f] = (
797 actions[f] = (
798 mergestatemod.ACTION_CREATED_MERGE,
798 mergestatemod.ACTION_CREATED_MERGE,
799 (fl2, pa.node()),
799 (fl2, pa.node()),
800 b'remote created, get or merge',
800 b'remote created, get or merge',
801 )
801 )
802 elif n2 != ma[f]:
802 elif n2 != ma[f]:
803 df = None
803 df = None
804 for d in branch_copies1.dirmove:
804 for d in branch_copies1.dirmove:
805 if f.startswith(d):
805 if f.startswith(d):
806 # new file added in a directory that was moved
806 # new file added in a directory that was moved
807 df = branch_copies1.dirmove[d] + f[len(d) :]
807 df = branch_copies1.dirmove[d] + f[len(d) :]
808 break
808 break
809 if df is not None and df in m1:
809 if df is not None and df in m1:
810 actions[df] = (
810 actions[df] = (
811 mergestatemod.ACTION_MERGE,
811 mergestatemod.ACTION_MERGE,
812 (df, f, f, False, pa.node()),
812 (df, f, f, False, pa.node()),
813 b'local directory rename - respect move '
813 b'local directory rename - respect move '
814 b'from %s' % f,
814 b'from %s' % f,
815 )
815 )
816 elif acceptremote:
816 elif acceptremote:
817 actions[f] = (
817 actions[f] = (
818 mergestatemod.ACTION_CREATED,
818 mergestatemod.ACTION_CREATED,
819 (fl2,),
819 (fl2,),
820 b'remote recreating',
820 b'remote recreating',
821 )
821 )
822 else:
822 else:
823 actions[f] = (
823 actions[f] = (
824 mergestatemod.ACTION_DELETED_CHANGED,
824 mergestatemod.ACTION_DELETED_CHANGED,
825 (None, f, f, False, pa.node()),
825 (None, f, f, False, pa.node()),
826 b'prompt deleted/changed',
826 b'prompt deleted/changed',
827 )
827 )
828
828
829 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
829 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
830 # If we are merging, look for path conflicts.
830 # If we are merging, look for path conflicts.
831 checkpathconflicts(repo, wctx, p2, actions)
831 checkpathconflicts(repo, wctx, p2, actions)
832
832
833 narrowmatch = repo.narrowmatch()
833 narrowmatch = repo.narrowmatch()
834 if not narrowmatch.always():
834 if not narrowmatch.always():
835 # Updates "actions" in place
835 # Updates "actions" in place
836 _filternarrowactions(narrowmatch, branchmerge, actions)
836 _filternarrowactions(narrowmatch, branchmerge, actions)
837
837
838 renamedelete = branch_copies1.renamedelete
838 renamedelete = branch_copies1.renamedelete
839 renamedelete.update(branch_copies2.renamedelete)
839 renamedelete.update(branch_copies2.renamedelete)
840
840
841 return actions, diverge, renamedelete
841 return actions, diverge, renamedelete
842
842
843
843
844 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
844 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
845 """Resolves false conflicts where the nodeid changed but the content
845 """Resolves false conflicts where the nodeid changed but the content
846 remained the same."""
846 remained the same."""
847 # We force a copy of actions.items() because we're going to mutate
847 # We force a copy of actions.items() because we're going to mutate
848 # actions as we resolve trivial conflicts.
848 # actions as we resolve trivial conflicts.
849 for f, (m, args, msg) in list(actions.items()):
849 for f, (m, args, msg) in list(actions.items()):
850 if (
850 if (
851 m == mergestatemod.ACTION_CHANGED_DELETED
851 m == mergestatemod.ACTION_CHANGED_DELETED
852 and f in ancestor
852 and f in ancestor
853 and not wctx[f].cmp(ancestor[f])
853 and not wctx[f].cmp(ancestor[f])
854 ):
854 ):
855 # local did change but ended up with same content
855 # local did change but ended up with same content
856 actions[f] = mergestatemod.ACTION_REMOVE, None, b'prompt same'
856 actions[f] = mergestatemod.ACTION_REMOVE, None, b'prompt same'
857 elif (
857 elif (
858 m == mergestatemod.ACTION_DELETED_CHANGED
858 m == mergestatemod.ACTION_DELETED_CHANGED
859 and f in ancestor
859 and f in ancestor
860 and not mctx[f].cmp(ancestor[f])
860 and not mctx[f].cmp(ancestor[f])
861 ):
861 ):
862 # remote did change but ended up with same content
862 # remote did change but ended up with same content
863 del actions[f] # don't get = keep local deleted
863 del actions[f] # don't get = keep local deleted
864
864
865
865
866 def calculateupdates(
866 def calculateupdates(
867 repo,
867 repo,
868 wctx,
868 wctx,
869 mctx,
869 mctx,
870 ancestors,
870 ancestors,
871 branchmerge,
871 branchmerge,
872 force,
872 force,
873 acceptremote,
873 acceptremote,
874 followcopies,
874 followcopies,
875 matcher=None,
875 matcher=None,
876 mergeforce=False,
876 mergeforce=False,
877 ):
877 ):
878 """Calculate the actions needed to merge mctx into wctx using ancestors"""
878 """Calculate the actions needed to merge mctx into wctx using ancestors"""
879 # Avoid cycle.
879 # Avoid cycle.
880 from . import sparse
880 from . import sparse
881
881
882 if len(ancestors) == 1: # default
882 if len(ancestors) == 1: # default
883 actions, diverge, renamedelete = manifestmerge(
883 actions, diverge, renamedelete = manifestmerge(
884 repo,
884 repo,
885 wctx,
885 wctx,
886 mctx,
886 mctx,
887 ancestors[0],
887 ancestors[0],
888 branchmerge,
888 branchmerge,
889 force,
889 force,
890 matcher,
890 matcher,
891 acceptremote,
891 acceptremote,
892 followcopies,
892 followcopies,
893 )
893 )
894 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
894 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
895
895
896 else: # only when merge.preferancestor=* - the default
896 else: # only when merge.preferancestor=* - the default
897 repo.ui.note(
897 repo.ui.note(
898 _(b"note: merging %s and %s using bids from ancestors %s\n")
898 _(b"note: merging %s and %s using bids from ancestors %s\n")
899 % (
899 % (
900 wctx,
900 wctx,
901 mctx,
901 mctx,
902 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
902 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
903 )
903 )
904 )
904 )
905
905
906 # Call for bids
906 # Call for bids
907 fbids = (
907 fbids = (
908 {}
908 {}
909 ) # mapping filename to bids (action method to list af actions)
909 ) # mapping filename to bids (action method to list af actions)
910 diverge, renamedelete = None, None
910 diverge, renamedelete = None, None
911 for ancestor in ancestors:
911 for ancestor in ancestors:
912 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
912 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
913 actions, diverge1, renamedelete1 = manifestmerge(
913 actions, diverge1, renamedelete1 = manifestmerge(
914 repo,
914 repo,
915 wctx,
915 wctx,
916 mctx,
916 mctx,
917 ancestor,
917 ancestor,
918 branchmerge,
918 branchmerge,
919 force,
919 force,
920 matcher,
920 matcher,
921 acceptremote,
921 acceptremote,
922 followcopies,
922 followcopies,
923 forcefulldiff=True,
923 forcefulldiff=True,
924 )
924 )
925 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
925 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
926
926
927 # Track the shortest set of warning on the theory that bid
927 # Track the shortest set of warning on the theory that bid
928 # merge will correctly incorporate more information
928 # merge will correctly incorporate more information
929 if diverge is None or len(diverge1) < len(diverge):
929 if diverge is None or len(diverge1) < len(diverge):
930 diverge = diverge1
930 diverge = diverge1
931 if renamedelete is None or len(renamedelete) < len(renamedelete1):
931 if renamedelete is None or len(renamedelete) < len(renamedelete1):
932 renamedelete = renamedelete1
932 renamedelete = renamedelete1
933
933
934 for f, a in sorted(pycompat.iteritems(actions)):
934 for f, a in sorted(pycompat.iteritems(actions)):
935 m, args, msg = a
935 m, args, msg = a
936 if m == mergestatemod.ACTION_GET_OTHER_AND_STORE:
936 if m == mergestatemod.ACTION_GET_OTHER_AND_STORE:
937 m = mergestatemod.ACTION_GET
937 m = mergestatemod.ACTION_GET
938 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
938 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
939 if f in fbids:
939 if f in fbids:
940 d = fbids[f]
940 d = fbids[f]
941 if m in d:
941 if m in d:
942 d[m].append(a)
942 d[m].append(a)
943 else:
943 else:
944 d[m] = [a]
944 d[m] = [a]
945 else:
945 else:
946 fbids[f] = {m: [a]}
946 fbids[f] = {m: [a]}
947
947
948 # Pick the best bid for each file
948 # Pick the best bid for each file
949 repo.ui.note(_(b'\nauction for merging merge bids\n'))
949 repo.ui.note(_(b'\nauction for merging merge bids\n'))
950 actions = {}
950 actions = {}
951 for f, bids in sorted(fbids.items()):
951 for f, bids in sorted(fbids.items()):
952 # bids is a mapping from action method to list af actions
952 # bids is a mapping from action method to list af actions
953 # Consensus?
953 # Consensus?
954 if len(bids) == 1: # all bids are the same kind of method
954 if len(bids) == 1: # all bids are the same kind of method
955 m, l = list(bids.items())[0]
955 m, l = list(bids.items())[0]
956 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
956 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
957 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
957 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
958 actions[f] = l[0]
958 actions[f] = l[0]
959 continue
959 continue
960 # If keep is an option, just do it.
960 # If keep is an option, just do it.
961 if mergestatemod.ACTION_KEEP in bids:
961 if mergestatemod.ACTION_KEEP in bids:
962 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
962 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
963 actions[f] = bids[mergestatemod.ACTION_KEEP][0]
963 actions[f] = bids[mergestatemod.ACTION_KEEP][0]
964 continue
964 continue
965 # If there are gets and they all agree [how could they not?], do it.
965 # If there are gets and they all agree [how could they not?], do it.
966 if mergestatemod.ACTION_GET in bids:
966 if mergestatemod.ACTION_GET in bids:
967 ga0 = bids[mergestatemod.ACTION_GET][0]
967 ga0 = bids[mergestatemod.ACTION_GET][0]
968 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
968 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
969 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
969 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
970 actions[f] = ga0
970 actions[f] = ga0
971 continue
971 continue
972 # TODO: Consider other simple actions such as mode changes
972 # TODO: Consider other simple actions such as mode changes
973 # Handle inefficient democrazy.
973 # Handle inefficient democrazy.
974 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
974 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
975 for m, l in sorted(bids.items()):
975 for m, l in sorted(bids.items()):
976 for _f, args, msg in l:
976 for _f, args, msg in l:
977 repo.ui.note(b' %s -> %s\n' % (msg, m))
977 repo.ui.note(b' %s -> %s\n' % (msg, m))
978 # Pick random action. TODO: Instead, prompt user when resolving
978 # Pick random action. TODO: Instead, prompt user when resolving
979 m, l = list(bids.items())[0]
979 m, l = list(bids.items())[0]
980 repo.ui.warn(
980 repo.ui.warn(
981 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
981 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
982 )
982 )
983 actions[f] = l[0]
983 actions[f] = l[0]
984 continue
984 continue
985 repo.ui.note(_(b'end of auction\n\n'))
985 repo.ui.note(_(b'end of auction\n\n'))
986
986
987 if wctx.rev() is None:
987 if wctx.rev() is None:
988 fractions = _forgetremoved(wctx, mctx, branchmerge)
988 fractions = _forgetremoved(wctx, mctx, branchmerge)
989 actions.update(fractions)
989 actions.update(fractions)
990
990
991 prunedactions = sparse.filterupdatesactions(
991 prunedactions = sparse.filterupdatesactions(
992 repo, wctx, mctx, branchmerge, actions
992 repo, wctx, mctx, branchmerge, actions
993 )
993 )
994 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
994 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
995
995
996 return prunedactions, diverge, renamedelete
996 return prunedactions, diverge, renamedelete
997
997
998
998
999 def _getcwd():
999 def _getcwd():
1000 try:
1000 try:
1001 return encoding.getcwd()
1001 return encoding.getcwd()
1002 except OSError as err:
1002 except OSError as err:
1003 if err.errno == errno.ENOENT:
1003 if err.errno == errno.ENOENT:
1004 return None
1004 return None
1005 raise
1005 raise
1006
1006
1007
1007
1008 def batchremove(repo, wctx, actions):
1008 def batchremove(repo, wctx, actions):
1009 """apply removes to the working directory
1009 """apply removes to the working directory
1010
1010
1011 yields tuples for progress updates
1011 yields tuples for progress updates
1012 """
1012 """
1013 verbose = repo.ui.verbose
1013 verbose = repo.ui.verbose
1014 cwd = _getcwd()
1014 cwd = _getcwd()
1015 i = 0
1015 i = 0
1016 for f, args, msg in actions:
1016 for f, args, msg in actions:
1017 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1017 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1018 if verbose:
1018 if verbose:
1019 repo.ui.note(_(b"removing %s\n") % f)
1019 repo.ui.note(_(b"removing %s\n") % f)
1020 wctx[f].audit()
1020 wctx[f].audit()
1021 try:
1021 try:
1022 wctx[f].remove(ignoremissing=True)
1022 wctx[f].remove(ignoremissing=True)
1023 except OSError as inst:
1023 except OSError as inst:
1024 repo.ui.warn(
1024 repo.ui.warn(
1025 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1025 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1026 )
1026 )
1027 if i == 100:
1027 if i == 100:
1028 yield i, f
1028 yield i, f
1029 i = 0
1029 i = 0
1030 i += 1
1030 i += 1
1031 if i > 0:
1031 if i > 0:
1032 yield i, f
1032 yield i, f
1033
1033
1034 if cwd and not _getcwd():
1034 if cwd and not _getcwd():
1035 # cwd was removed in the course of removing files; print a helpful
1035 # cwd was removed in the course of removing files; print a helpful
1036 # warning.
1036 # warning.
1037 repo.ui.warn(
1037 repo.ui.warn(
1038 _(
1038 _(
1039 b"current directory was removed\n"
1039 b"current directory was removed\n"
1040 b"(consider changing to repo root: %s)\n"
1040 b"(consider changing to repo root: %s)\n"
1041 )
1041 )
1042 % repo.root
1042 % repo.root
1043 )
1043 )
1044
1044
1045
1045
1046 def batchget(repo, mctx, wctx, wantfiledata, actions):
1046 def batchget(repo, mctx, wctx, wantfiledata, actions):
1047 """apply gets to the working directory
1047 """apply gets to the working directory
1048
1048
1049 mctx is the context to get from
1049 mctx is the context to get from
1050
1050
1051 Yields arbitrarily many (False, tuple) for progress updates, followed by
1051 Yields arbitrarily many (False, tuple) for progress updates, followed by
1052 exactly one (True, filedata). When wantfiledata is false, filedata is an
1052 exactly one (True, filedata). When wantfiledata is false, filedata is an
1053 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1053 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1054 mtime) of the file f written for each action.
1054 mtime) of the file f written for each action.
1055 """
1055 """
1056 filedata = {}
1056 filedata = {}
1057 verbose = repo.ui.verbose
1057 verbose = repo.ui.verbose
1058 fctx = mctx.filectx
1058 fctx = mctx.filectx
1059 ui = repo.ui
1059 ui = repo.ui
1060 i = 0
1060 i = 0
1061 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1061 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1062 for f, (flags, backup), msg in actions:
1062 for f, (flags, backup), msg in actions:
1063 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1063 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1064 if verbose:
1064 if verbose:
1065 repo.ui.note(_(b"getting %s\n") % f)
1065 repo.ui.note(_(b"getting %s\n") % f)
1066
1066
1067 if backup:
1067 if backup:
1068 # If a file or directory exists with the same name, back that
1068 # If a file or directory exists with the same name, back that
1069 # up. Otherwise, look to see if there is a file that conflicts
1069 # up. Otherwise, look to see if there is a file that conflicts
1070 # with a directory this file is in, and if so, back that up.
1070 # with a directory this file is in, and if so, back that up.
1071 conflicting = f
1071 conflicting = f
1072 if not repo.wvfs.lexists(f):
1072 if not repo.wvfs.lexists(f):
1073 for p in pathutil.finddirs(f):
1073 for p in pathutil.finddirs(f):
1074 if repo.wvfs.isfileorlink(p):
1074 if repo.wvfs.isfileorlink(p):
1075 conflicting = p
1075 conflicting = p
1076 break
1076 break
1077 if repo.wvfs.lexists(conflicting):
1077 if repo.wvfs.lexists(conflicting):
1078 orig = scmutil.backuppath(ui, repo, conflicting)
1078 orig = scmutil.backuppath(ui, repo, conflicting)
1079 util.rename(repo.wjoin(conflicting), orig)
1079 util.rename(repo.wjoin(conflicting), orig)
1080 wfctx = wctx[f]
1080 wfctx = wctx[f]
1081 wfctx.clearunknown()
1081 wfctx.clearunknown()
1082 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1082 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1083 size = wfctx.write(
1083 size = wfctx.write(
1084 fctx(f).data(),
1084 fctx(f).data(),
1085 flags,
1085 flags,
1086 backgroundclose=True,
1086 backgroundclose=True,
1087 atomictemp=atomictemp,
1087 atomictemp=atomictemp,
1088 )
1088 )
1089 if wantfiledata:
1089 if wantfiledata:
1090 s = wfctx.lstat()
1090 s = wfctx.lstat()
1091 mode = s.st_mode
1091 mode = s.st_mode
1092 mtime = s[stat.ST_MTIME]
1092 mtime = s[stat.ST_MTIME]
1093 filedata[f] = (mode, size, mtime) # for dirstate.normal
1093 filedata[f] = (mode, size, mtime) # for dirstate.normal
1094 if i == 100:
1094 if i == 100:
1095 yield False, (i, f)
1095 yield False, (i, f)
1096 i = 0
1096 i = 0
1097 i += 1
1097 i += 1
1098 if i > 0:
1098 if i > 0:
1099 yield False, (i, f)
1099 yield False, (i, f)
1100 yield True, filedata
1100 yield True, filedata
1101
1101
1102
1102
1103 def _prefetchfiles(repo, ctx, actions):
1103 def _prefetchfiles(repo, ctx, actions):
1104 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1104 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1105 of merge actions. ``ctx`` is the context being merged in."""
1105 of merge actions. ``ctx`` is the context being merged in."""
1106
1106
1107 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1107 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1108 # don't touch the context to be merged in. 'cd' is skipped, because
1108 # don't touch the context to be merged in. 'cd' is skipped, because
1109 # changed/deleted never resolves to something from the remote side.
1109 # changed/deleted never resolves to something from the remote side.
1110 oplist = [
1110 oplist = [
1111 actions[a]
1111 actions[a]
1112 for a in (
1112 for a in (
1113 mergestatemod.ACTION_GET,
1113 mergestatemod.ACTION_GET,
1114 mergestatemod.ACTION_DELETED_CHANGED,
1114 mergestatemod.ACTION_DELETED_CHANGED,
1115 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1115 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1116 mergestatemod.ACTION_MERGE,
1116 mergestatemod.ACTION_MERGE,
1117 )
1117 )
1118 ]
1118 ]
1119 prefetch = scmutil.prefetchfiles
1119 prefetch = scmutil.prefetchfiles
1120 matchfiles = scmutil.matchfiles
1120 matchfiles = scmutil.matchfiles
1121 prefetch(
1121 prefetch(
1122 repo,
1122 repo,
1123 [
1123 [
1124 (
1124 (
1125 ctx.rev(),
1125 ctx.rev(),
1126 matchfiles(
1126 matchfiles(
1127 repo, [f for sublist in oplist for f, args, msg in sublist]
1127 repo, [f for sublist in oplist for f, args, msg in sublist]
1128 ),
1128 ),
1129 )
1129 )
1130 ],
1130 ],
1131 )
1131 )
1132
1132
1133
1133
1134 @attr.s(frozen=True)
1134 @attr.s(frozen=True)
1135 class updateresult(object):
1135 class updateresult(object):
1136 updatedcount = attr.ib()
1136 updatedcount = attr.ib()
1137 mergedcount = attr.ib()
1137 mergedcount = attr.ib()
1138 removedcount = attr.ib()
1138 removedcount = attr.ib()
1139 unresolvedcount = attr.ib()
1139 unresolvedcount = attr.ib()
1140
1140
1141 def isempty(self):
1141 def isempty(self):
1142 return not (
1142 return not (
1143 self.updatedcount
1143 self.updatedcount
1144 or self.mergedcount
1144 or self.mergedcount
1145 or self.removedcount
1145 or self.removedcount
1146 or self.unresolvedcount
1146 or self.unresolvedcount
1147 )
1147 )
1148
1148
1149
1149
1150 def emptyactions():
1150 def emptyactions():
1151 """create an actions dict, to be populated and passed to applyupdates()"""
1151 """create an actions dict, to be populated and passed to applyupdates()"""
1152 return {
1152 return {
1153 m: []
1153 m: []
1154 for m in (
1154 for m in (
1155 mergestatemod.ACTION_ADD,
1155 mergestatemod.ACTION_ADD,
1156 mergestatemod.ACTION_ADD_MODIFIED,
1156 mergestatemod.ACTION_ADD_MODIFIED,
1157 mergestatemod.ACTION_FORGET,
1157 mergestatemod.ACTION_FORGET,
1158 mergestatemod.ACTION_GET,
1158 mergestatemod.ACTION_GET,
1159 mergestatemod.ACTION_CHANGED_DELETED,
1159 mergestatemod.ACTION_CHANGED_DELETED,
1160 mergestatemod.ACTION_DELETED_CHANGED,
1160 mergestatemod.ACTION_DELETED_CHANGED,
1161 mergestatemod.ACTION_REMOVE,
1161 mergestatemod.ACTION_REMOVE,
1162 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1162 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1163 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1163 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1164 mergestatemod.ACTION_MERGE,
1164 mergestatemod.ACTION_MERGE,
1165 mergestatemod.ACTION_EXEC,
1165 mergestatemod.ACTION_EXEC,
1166 mergestatemod.ACTION_KEEP,
1166 mergestatemod.ACTION_KEEP,
1167 mergestatemod.ACTION_PATH_CONFLICT,
1167 mergestatemod.ACTION_PATH_CONFLICT,
1168 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1168 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1169 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1169 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1170 )
1170 )
1171 }
1171 }
1172
1172
1173
1173
1174 def applyupdates(
1174 def applyupdates(
1175 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1175 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1176 ):
1176 ):
1177 """apply the merge action list to the working directory
1177 """apply the merge action list to the working directory
1178
1178
1179 wctx is the working copy context
1179 wctx is the working copy context
1180 mctx is the context to be merged into the working copy
1180 mctx is the context to be merged into the working copy
1181
1181
1182 Return a tuple of (counts, filedata), where counts is a tuple
1182 Return a tuple of (counts, filedata), where counts is a tuple
1183 (updated, merged, removed, unresolved) that describes how many
1183 (updated, merged, removed, unresolved) that describes how many
1184 files were affected by the update, and filedata is as described in
1184 files were affected by the update, and filedata is as described in
1185 batchget.
1185 batchget.
1186 """
1186 """
1187
1187
1188 _prefetchfiles(repo, mctx, actions)
1188 _prefetchfiles(repo, mctx, actions)
1189
1189
1190 updated, merged, removed = 0, 0, 0
1190 updated, merged, removed = 0, 0, 0
1191 ms = mergestatemod.mergestate.clean(
1191 ms = mergestatemod.mergestate.clean(
1192 repo, wctx.p1().node(), mctx.node(), labels
1192 repo, wctx.p1().node(), mctx.node(), labels
1193 )
1193 )
1194
1194
1195 # add ACTION_GET_OTHER_AND_STORE to mergestate
1195 # add ACTION_GET_OTHER_AND_STORE to mergestate
1196 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1196 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1197 ms.addmergedother(e[0])
1197 ms.addmergedother(e[0])
1198
1198
1199 moves = []
1199 moves = []
1200 for m, l in actions.items():
1200 for m, l in actions.items():
1201 l.sort()
1201 l.sort()
1202
1202
1203 # 'cd' and 'dc' actions are treated like other merge conflicts
1203 # 'cd' and 'dc' actions are treated like other merge conflicts
1204 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1204 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1205 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1205 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1206 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1206 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1207 for f, args, msg in mergeactions:
1207 for f, args, msg in mergeactions:
1208 f1, f2, fa, move, anc = args
1208 f1, f2, fa, move, anc = args
1209 if f == b'.hgsubstate': # merged internally
1209 if f == b'.hgsubstate': # merged internally
1210 continue
1210 continue
1211 if f1 is None:
1211 if f1 is None:
1212 fcl = filemerge.absentfilectx(wctx, fa)
1212 fcl = filemerge.absentfilectx(wctx, fa)
1213 else:
1213 else:
1214 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1214 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1215 fcl = wctx[f1]
1215 fcl = wctx[f1]
1216 if f2 is None:
1216 if f2 is None:
1217 fco = filemerge.absentfilectx(mctx, fa)
1217 fco = filemerge.absentfilectx(mctx, fa)
1218 else:
1218 else:
1219 fco = mctx[f2]
1219 fco = mctx[f2]
1220 actx = repo[anc]
1220 actx = repo[anc]
1221 if fa in actx:
1221 if fa in actx:
1222 fca = actx[fa]
1222 fca = actx[fa]
1223 else:
1223 else:
1224 # TODO: move to absentfilectx
1224 # TODO: move to absentfilectx
1225 fca = repo.filectx(f1, fileid=nullrev)
1225 fca = repo.filectx(f1, fileid=nullrev)
1226 ms.add(fcl, fco, fca, f)
1226 ms.add(fcl, fco, fca, f)
1227 if f1 != f and move:
1227 if f1 != f and move:
1228 moves.append(f1)
1228 moves.append(f1)
1229
1229
1230 # remove renamed files after safely stored
1230 # remove renamed files after safely stored
1231 for f in moves:
1231 for f in moves:
1232 if wctx[f].lexists():
1232 if wctx[f].lexists():
1233 repo.ui.debug(b"removing %s\n" % f)
1233 repo.ui.debug(b"removing %s\n" % f)
1234 wctx[f].audit()
1234 wctx[f].audit()
1235 wctx[f].remove()
1235 wctx[f].remove()
1236
1236
1237 numupdates = sum(
1237 numupdates = sum(
1238 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1238 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1239 )
1239 )
1240 progress = repo.ui.makeprogress(
1240 progress = repo.ui.makeprogress(
1241 _(b'updating'), unit=_(b'files'), total=numupdates
1241 _(b'updating'), unit=_(b'files'), total=numupdates
1242 )
1242 )
1243
1243
1244 if [
1244 if [
1245 a
1245 a
1246 for a in actions[mergestatemod.ACTION_REMOVE]
1246 for a in actions[mergestatemod.ACTION_REMOVE]
1247 if a[0] == b'.hgsubstate'
1247 if a[0] == b'.hgsubstate'
1248 ]:
1248 ]:
1249 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1249 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1250
1250
1251 # record path conflicts
1251 # record path conflicts
1252 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1252 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1253 f1, fo = args
1253 f1, fo = args
1254 s = repo.ui.status
1254 s = repo.ui.status
1255 s(
1255 s(
1256 _(
1256 _(
1257 b"%s: path conflict - a file or link has the same name as a "
1257 b"%s: path conflict - a file or link has the same name as a "
1258 b"directory\n"
1258 b"directory\n"
1259 )
1259 )
1260 % f
1260 % f
1261 )
1261 )
1262 if fo == b'l':
1262 if fo == b'l':
1263 s(_(b"the local file has been renamed to %s\n") % f1)
1263 s(_(b"the local file has been renamed to %s\n") % f1)
1264 else:
1264 else:
1265 s(_(b"the remote file has been renamed to %s\n") % f1)
1265 s(_(b"the remote file has been renamed to %s\n") % f1)
1266 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1266 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1267 ms.addpathconflict(f, f1, fo)
1267 ms.addpathconflict(f, f1, fo)
1268 progress.increment(item=f)
1268 progress.increment(item=f)
1269
1269
1270 # When merging in-memory, we can't support worker processes, so set the
1270 # When merging in-memory, we can't support worker processes, so set the
1271 # per-item cost at 0 in that case.
1271 # per-item cost at 0 in that case.
1272 cost = 0 if wctx.isinmemory() else 0.001
1272 cost = 0 if wctx.isinmemory() else 0.001
1273
1273
1274 # remove in parallel (must come before resolving path conflicts and getting)
1274 # remove in parallel (must come before resolving path conflicts and getting)
1275 prog = worker.worker(
1275 prog = worker.worker(
1276 repo.ui,
1276 repo.ui,
1277 cost,
1277 cost,
1278 batchremove,
1278 batchremove,
1279 (repo, wctx),
1279 (repo, wctx),
1280 actions[mergestatemod.ACTION_REMOVE],
1280 actions[mergestatemod.ACTION_REMOVE],
1281 )
1281 )
1282 for i, item in prog:
1282 for i, item in prog:
1283 progress.increment(step=i, item=item)
1283 progress.increment(step=i, item=item)
1284 removed = len(actions[mergestatemod.ACTION_REMOVE])
1284 removed = len(actions[mergestatemod.ACTION_REMOVE])
1285
1285
1286 # resolve path conflicts (must come before getting)
1286 # resolve path conflicts (must come before getting)
1287 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1287 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1288 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1288 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1289 (f0, origf0) = args
1289 (f0, origf0) = args
1290 if wctx[f0].lexists():
1290 if wctx[f0].lexists():
1291 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1291 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1292 wctx[f].audit()
1292 wctx[f].audit()
1293 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1293 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1294 wctx[f0].remove()
1294 wctx[f0].remove()
1295 progress.increment(item=f)
1295 progress.increment(item=f)
1296
1296
1297 # get in parallel.
1297 # get in parallel.
1298 threadsafe = repo.ui.configbool(
1298 threadsafe = repo.ui.configbool(
1299 b'experimental', b'worker.wdir-get-thread-safe'
1299 b'experimental', b'worker.wdir-get-thread-safe'
1300 )
1300 )
1301 prog = worker.worker(
1301 prog = worker.worker(
1302 repo.ui,
1302 repo.ui,
1303 cost,
1303 cost,
1304 batchget,
1304 batchget,
1305 (repo, mctx, wctx, wantfiledata),
1305 (repo, mctx, wctx, wantfiledata),
1306 actions[mergestatemod.ACTION_GET],
1306 actions[mergestatemod.ACTION_GET],
1307 threadsafe=threadsafe,
1307 threadsafe=threadsafe,
1308 hasretval=True,
1308 hasretval=True,
1309 )
1309 )
1310 getfiledata = {}
1310 getfiledata = {}
1311 for final, res in prog:
1311 for final, res in prog:
1312 if final:
1312 if final:
1313 getfiledata = res
1313 getfiledata = res
1314 else:
1314 else:
1315 i, item = res
1315 i, item = res
1316 progress.increment(step=i, item=item)
1316 progress.increment(step=i, item=item)
1317 updated = len(actions[mergestatemod.ACTION_GET])
1317 updated = len(actions[mergestatemod.ACTION_GET])
1318
1318
1319 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1319 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1320 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1320 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1321
1321
1322 # forget (manifest only, just log it) (must come first)
1322 # forget (manifest only, just log it) (must come first)
1323 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1323 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1324 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1324 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1325 progress.increment(item=f)
1325 progress.increment(item=f)
1326
1326
1327 # re-add (manifest only, just log it)
1327 # re-add (manifest only, just log it)
1328 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1328 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1329 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1329 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1330 progress.increment(item=f)
1330 progress.increment(item=f)
1331
1331
1332 # re-add/mark as modified (manifest only, just log it)
1332 # re-add/mark as modified (manifest only, just log it)
1333 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1333 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1334 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1334 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1335 progress.increment(item=f)
1335 progress.increment(item=f)
1336
1336
1337 # keep (noop, just log it)
1337 # keep (noop, just log it)
1338 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
1338 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
1339 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1339 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1340 # no progress
1340 # no progress
1341
1341
1342 # directory rename, move local
1342 # directory rename, move local
1343 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1343 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1344 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1344 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1345 progress.increment(item=f)
1345 progress.increment(item=f)
1346 f0, flags = args
1346 f0, flags = args
1347 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1347 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1348 wctx[f].audit()
1348 wctx[f].audit()
1349 wctx[f].write(wctx.filectx(f0).data(), flags)
1349 wctx[f].write(wctx.filectx(f0).data(), flags)
1350 wctx[f0].remove()
1350 wctx[f0].remove()
1351 updated += 1
1351 updated += 1
1352
1352
1353 # local directory rename, get
1353 # local directory rename, get
1354 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1354 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1355 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1355 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1356 progress.increment(item=f)
1356 progress.increment(item=f)
1357 f0, flags = args
1357 f0, flags = args
1358 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1358 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1359 wctx[f].write(mctx.filectx(f0).data(), flags)
1359 wctx[f].write(mctx.filectx(f0).data(), flags)
1360 updated += 1
1360 updated += 1
1361
1361
1362 # exec
1362 # exec
1363 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
1363 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
1364 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1364 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1365 progress.increment(item=f)
1365 progress.increment(item=f)
1366 (flags,) = args
1366 (flags,) = args
1367 wctx[f].audit()
1367 wctx[f].audit()
1368 wctx[f].setflags(b'l' in flags, b'x' in flags)
1368 wctx[f].setflags(b'l' in flags, b'x' in flags)
1369 updated += 1
1369 updated += 1
1370
1370
1371 # the ordering is important here -- ms.mergedriver will raise if the merge
1371 # the ordering is important here -- ms.mergedriver will raise if the merge
1372 # driver has changed, and we want to be able to bypass it when overwrite is
1372 # driver has changed, and we want to be able to bypass it when overwrite is
1373 # True
1373 # True
1374 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1374 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1375
1375
1376 if usemergedriver:
1376 if usemergedriver:
1377 if wctx.isinmemory():
1377 if wctx.isinmemory():
1378 raise error.InMemoryMergeConflictsError(
1378 raise error.InMemoryMergeConflictsError(
1379 b"in-memory merge does not support mergedriver"
1379 b"in-memory merge does not support mergedriver"
1380 )
1380 )
1381 ms.commit()
1381 ms.commit()
1382 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1382 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1383 # the driver might leave some files unresolved
1383 # the driver might leave some files unresolved
1384 unresolvedf = set(ms.unresolved())
1384 unresolvedf = set(ms.unresolved())
1385 if not proceed:
1385 if not proceed:
1386 # XXX setting unresolved to at least 1 is a hack to make sure we
1386 # XXX setting unresolved to at least 1 is a hack to make sure we
1387 # error out
1387 # error out
1388 return updateresult(
1388 return updateresult(
1389 updated, merged, removed, max(len(unresolvedf), 1)
1389 updated, merged, removed, max(len(unresolvedf), 1)
1390 )
1390 )
1391 newactions = []
1391 newactions = []
1392 for f, args, msg in mergeactions:
1392 for f, args, msg in mergeactions:
1393 if f in unresolvedf:
1393 if f in unresolvedf:
1394 newactions.append((f, args, msg))
1394 newactions.append((f, args, msg))
1395 mergeactions = newactions
1395 mergeactions = newactions
1396
1396
1397 try:
1397 try:
1398 # premerge
1398 # premerge
1399 tocomplete = []
1399 tocomplete = []
1400 for f, args, msg in mergeactions:
1400 for f, args, msg in mergeactions:
1401 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1401 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1402 progress.increment(item=f)
1402 progress.increment(item=f)
1403 if f == b'.hgsubstate': # subrepo states need updating
1403 if f == b'.hgsubstate': # subrepo states need updating
1404 subrepoutil.submerge(
1404 subrepoutil.submerge(
1405 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1405 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1406 )
1406 )
1407 continue
1407 continue
1408 wctx[f].audit()
1408 wctx[f].audit()
1409 complete, r = ms.preresolve(f, wctx)
1409 complete, r = ms.preresolve(f, wctx)
1410 if not complete:
1410 if not complete:
1411 numupdates += 1
1411 numupdates += 1
1412 tocomplete.append((f, args, msg))
1412 tocomplete.append((f, args, msg))
1413
1413
1414 # merge
1414 # merge
1415 for f, args, msg in tocomplete:
1415 for f, args, msg in tocomplete:
1416 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1416 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1417 progress.increment(item=f, total=numupdates)
1417 progress.increment(item=f, total=numupdates)
1418 ms.resolve(f, wctx)
1418 ms.resolve(f, wctx)
1419
1419
1420 finally:
1420 finally:
1421 ms.commit()
1421 ms.commit()
1422
1422
1423 unresolved = ms.unresolvedcount()
1423 unresolved = ms.unresolvedcount()
1424
1424
1425 if (
1425 if (
1426 usemergedriver
1426 usemergedriver
1427 and not unresolved
1427 and not unresolved
1428 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1428 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1429 ):
1429 ):
1430 if not driverconclude(repo, ms, wctx, labels=labels):
1430 if not driverconclude(repo, ms, wctx, labels=labels):
1431 # XXX setting unresolved to at least 1 is a hack to make sure we
1431 # XXX setting unresolved to at least 1 is a hack to make sure we
1432 # error out
1432 # error out
1433 unresolved = max(unresolved, 1)
1433 unresolved = max(unresolved, 1)
1434
1434
1435 ms.commit()
1435 ms.commit()
1436
1436
1437 msupdated, msmerged, msremoved = ms.counts()
1437 msupdated, msmerged, msremoved = ms.counts()
1438 updated += msupdated
1438 updated += msupdated
1439 merged += msmerged
1439 merged += msmerged
1440 removed += msremoved
1440 removed += msremoved
1441
1441
1442 extraactions = ms.actions()
1442 extraactions = ms.actions()
1443 if extraactions:
1443 if extraactions:
1444 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
1444 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
1445 for k, acts in pycompat.iteritems(extraactions):
1445 for k, acts in pycompat.iteritems(extraactions):
1446 actions[k].extend(acts)
1446 actions[k].extend(acts)
1447 if k == mergestatemod.ACTION_GET and wantfiledata:
1447 if k == mergestatemod.ACTION_GET and wantfiledata:
1448 # no filedata until mergestate is updated to provide it
1448 # no filedata until mergestate is updated to provide it
1449 for a in acts:
1449 for a in acts:
1450 getfiledata[a[0]] = None
1450 getfiledata[a[0]] = None
1451 # Remove these files from actions[ACTION_MERGE] as well. This is
1451 # Remove these files from actions[ACTION_MERGE] as well. This is
1452 # important because in recordupdates, files in actions[ACTION_MERGE]
1452 # important because in recordupdates, files in actions[ACTION_MERGE]
1453 # are processed after files in other actions, and the merge driver
1453 # are processed after files in other actions, and the merge driver
1454 # might add files to those actions via extraactions above. This can
1454 # might add files to those actions via extraactions above. This can
1455 # lead to a file being recorded twice, with poor results. This is
1455 # lead to a file being recorded twice, with poor results. This is
1456 # especially problematic for actions[ACTION_REMOVE] (currently only
1456 # especially problematic for actions[ACTION_REMOVE] (currently only
1457 # possible with the merge driver in the initial merge process;
1457 # possible with the merge driver in the initial merge process;
1458 # interrupted merges don't go through this flow).
1458 # interrupted merges don't go through this flow).
1459 #
1459 #
1460 # The real fix here is to have indexes by both file and action so
1460 # The real fix here is to have indexes by both file and action so
1461 # that when the action for a file is changed it is automatically
1461 # that when the action for a file is changed it is automatically
1462 # reflected in the other action lists. But that involves a more
1462 # reflected in the other action lists. But that involves a more
1463 # complex data structure, so this will do for now.
1463 # complex data structure, so this will do for now.
1464 #
1464 #
1465 # We don't need to do the same operation for 'dc' and 'cd' because
1465 # We don't need to do the same operation for 'dc' and 'cd' because
1466 # those lists aren't consulted again.
1466 # those lists aren't consulted again.
1467 mfiles.difference_update(a[0] for a in acts)
1467 mfiles.difference_update(a[0] for a in acts)
1468
1468
1469 actions[mergestatemod.ACTION_MERGE] = [
1469 actions[mergestatemod.ACTION_MERGE] = [
1470 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
1470 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
1471 ]
1471 ]
1472
1472
1473 progress.complete()
1473 progress.complete()
1474 assert len(getfiledata) == (
1474 assert len(getfiledata) == (
1475 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1475 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1476 )
1476 )
1477 return updateresult(updated, merged, removed, unresolved), getfiledata
1477 return updateresult(updated, merged, removed, unresolved), getfiledata
1478
1478
1479
1479
1480 def _advertisefsmonitor(repo, num_gets, p1node):
1481 # Advertise fsmonitor when its presence could be useful.
1482 #
1483 # We only advertise when performing an update from an empty working
1484 # directory. This typically only occurs during initial clone.
1485 #
1486 # We give users a mechanism to disable the warning in case it is
1487 # annoying.
1488 #
1489 # We only allow on Linux and MacOS because that's where fsmonitor is
1490 # considered stable.
1491 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1492 fsmonitorthreshold = repo.ui.configint(
1493 b'fsmonitor', b'warn_update_file_count'
1494 )
1495 try:
1496 # avoid cycle: extensions -> cmdutil -> merge
1497 from . import extensions
1498
1499 extensions.find(b'fsmonitor')
1500 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1501 # We intentionally don't look at whether fsmonitor has disabled
1502 # itself because a) fsmonitor may have already printed a warning
1503 # b) we only care about the config state here.
1504 except KeyError:
1505 fsmonitorenabled = False
1506
1507 if (
1508 fsmonitorwarning
1509 and not fsmonitorenabled
1510 and p1node == nullid
1511 and num_gets >= fsmonitorthreshold
1512 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1513 ):
1514 repo.ui.warn(
1515 _(
1516 b'(warning: large working directory being used without '
1517 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1518 b'see "hg help -e fsmonitor")\n'
1519 )
1520 )
1521
1522
1480 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1523 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1481 UPDATECHECK_NONE = b'none'
1524 UPDATECHECK_NONE = b'none'
1482 UPDATECHECK_LINEAR = b'linear'
1525 UPDATECHECK_LINEAR = b'linear'
1483 UPDATECHECK_NO_CONFLICT = b'noconflict'
1526 UPDATECHECK_NO_CONFLICT = b'noconflict'
1484
1527
1485
1528
1486 def update(
1529 def update(
1487 repo,
1530 repo,
1488 node,
1531 node,
1489 branchmerge,
1532 branchmerge,
1490 force,
1533 force,
1491 ancestor=None,
1534 ancestor=None,
1492 mergeancestor=False,
1535 mergeancestor=False,
1493 labels=None,
1536 labels=None,
1494 matcher=None,
1537 matcher=None,
1495 mergeforce=False,
1538 mergeforce=False,
1496 updatedirstate=True,
1539 updatedirstate=True,
1497 updatecheck=None,
1540 updatecheck=None,
1498 wc=None,
1541 wc=None,
1499 ):
1542 ):
1500 """
1543 """
1501 Perform a merge between the working directory and the given node
1544 Perform a merge between the working directory and the given node
1502
1545
1503 node = the node to update to
1546 node = the node to update to
1504 branchmerge = whether to merge between branches
1547 branchmerge = whether to merge between branches
1505 force = whether to force branch merging or file overwriting
1548 force = whether to force branch merging or file overwriting
1506 matcher = a matcher to filter file lists (dirstate not updated)
1549 matcher = a matcher to filter file lists (dirstate not updated)
1507 mergeancestor = whether it is merging with an ancestor. If true,
1550 mergeancestor = whether it is merging with an ancestor. If true,
1508 we should accept the incoming changes for any prompts that occur.
1551 we should accept the incoming changes for any prompts that occur.
1509 If false, merging with an ancestor (fast-forward) is only allowed
1552 If false, merging with an ancestor (fast-forward) is only allowed
1510 between different named branches. This flag is used by rebase extension
1553 between different named branches. This flag is used by rebase extension
1511 as a temporary fix and should be avoided in general.
1554 as a temporary fix and should be avoided in general.
1512 labels = labels to use for base, local and other
1555 labels = labels to use for base, local and other
1513 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1556 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1514 this is True, then 'force' should be True as well.
1557 this is True, then 'force' should be True as well.
1515
1558
1516 The table below shows all the behaviors of the update command given the
1559 The table below shows all the behaviors of the update command given the
1517 -c/--check and -C/--clean or no options, whether the working directory is
1560 -c/--check and -C/--clean or no options, whether the working directory is
1518 dirty, whether a revision is specified, and the relationship of the parent
1561 dirty, whether a revision is specified, and the relationship of the parent
1519 rev to the target rev (linear or not). Match from top first. The -n
1562 rev to the target rev (linear or not). Match from top first. The -n
1520 option doesn't exist on the command line, but represents the
1563 option doesn't exist on the command line, but represents the
1521 experimental.updatecheck=noconflict option.
1564 experimental.updatecheck=noconflict option.
1522
1565
1523 This logic is tested by test-update-branches.t.
1566 This logic is tested by test-update-branches.t.
1524
1567
1525 -c -C -n -m dirty rev linear | result
1568 -c -C -n -m dirty rev linear | result
1526 y y * * * * * | (1)
1569 y y * * * * * | (1)
1527 y * y * * * * | (1)
1570 y * y * * * * | (1)
1528 y * * y * * * | (1)
1571 y * * y * * * | (1)
1529 * y y * * * * | (1)
1572 * y y * * * * | (1)
1530 * y * y * * * | (1)
1573 * y * y * * * | (1)
1531 * * y y * * * | (1)
1574 * * y y * * * | (1)
1532 * * * * * n n | x
1575 * * * * * n n | x
1533 * * * * n * * | ok
1576 * * * * n * * | ok
1534 n n n n y * y | merge
1577 n n n n y * y | merge
1535 n n n n y y n | (2)
1578 n n n n y y n | (2)
1536 n n n y y * * | merge
1579 n n n y y * * | merge
1537 n n y n y * * | merge if no conflict
1580 n n y n y * * | merge if no conflict
1538 n y n n y * * | discard
1581 n y n n y * * | discard
1539 y n n n y * * | (3)
1582 y n n n y * * | (3)
1540
1583
1541 x = can't happen
1584 x = can't happen
1542 * = don't-care
1585 * = don't-care
1543 1 = incompatible options (checked in commands.py)
1586 1 = incompatible options (checked in commands.py)
1544 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1587 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1545 3 = abort: uncommitted changes (checked in commands.py)
1588 3 = abort: uncommitted changes (checked in commands.py)
1546
1589
1547 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1590 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1548 to repo[None] if None is passed.
1591 to repo[None] if None is passed.
1549
1592
1550 Return the same tuple as applyupdates().
1593 Return the same tuple as applyupdates().
1551 """
1594 """
1552 # Avoid cycle.
1595 # Avoid cycle.
1553 from . import sparse
1596 from . import sparse
1554
1597
1555 # This function used to find the default destination if node was None, but
1598 # This function used to find the default destination if node was None, but
1556 # that's now in destutil.py.
1599 # that's now in destutil.py.
1557 assert node is not None
1600 assert node is not None
1558 if not branchmerge and not force:
1601 if not branchmerge and not force:
1559 # TODO: remove the default once all callers that pass branchmerge=False
1602 # TODO: remove the default once all callers that pass branchmerge=False
1560 # and force=False pass a value for updatecheck. We may want to allow
1603 # and force=False pass a value for updatecheck. We may want to allow
1561 # updatecheck='abort' to better suppport some of these callers.
1604 # updatecheck='abort' to better suppport some of these callers.
1562 if updatecheck is None:
1605 if updatecheck is None:
1563 updatecheck = UPDATECHECK_LINEAR
1606 updatecheck = UPDATECHECK_LINEAR
1564 if updatecheck not in (
1607 if updatecheck not in (
1565 UPDATECHECK_NONE,
1608 UPDATECHECK_NONE,
1566 UPDATECHECK_LINEAR,
1609 UPDATECHECK_LINEAR,
1567 UPDATECHECK_NO_CONFLICT,
1610 UPDATECHECK_NO_CONFLICT,
1568 ):
1611 ):
1569 raise ValueError(
1612 raise ValueError(
1570 r'Invalid updatecheck %r (can accept %r)'
1613 r'Invalid updatecheck %r (can accept %r)'
1571 % (
1614 % (
1572 updatecheck,
1615 updatecheck,
1573 (
1616 (
1574 UPDATECHECK_NONE,
1617 UPDATECHECK_NONE,
1575 UPDATECHECK_LINEAR,
1618 UPDATECHECK_LINEAR,
1576 UPDATECHECK_NO_CONFLICT,
1619 UPDATECHECK_NO_CONFLICT,
1577 ),
1620 ),
1578 )
1621 )
1579 )
1622 )
1580 if wc is not None and wc.isinmemory():
1623 if wc is not None and wc.isinmemory():
1581 maybe_wlock = util.nullcontextmanager()
1624 maybe_wlock = util.nullcontextmanager()
1582 else:
1625 else:
1583 maybe_wlock = repo.wlock()
1626 maybe_wlock = repo.wlock()
1584 with maybe_wlock:
1627 with maybe_wlock:
1585 if wc is None:
1628 if wc is None:
1586 wc = repo[None]
1629 wc = repo[None]
1587 pl = wc.parents()
1630 pl = wc.parents()
1588 p1 = pl[0]
1631 p1 = pl[0]
1589 p2 = repo[node]
1632 p2 = repo[node]
1590 if ancestor is not None:
1633 if ancestor is not None:
1591 pas = [repo[ancestor]]
1634 pas = [repo[ancestor]]
1592 else:
1635 else:
1593 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1636 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1594 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1637 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1595 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1638 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1596 else:
1639 else:
1597 pas = [p1.ancestor(p2, warn=branchmerge)]
1640 pas = [p1.ancestor(p2, warn=branchmerge)]
1598
1641
1599 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1642 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1600
1643
1601 overwrite = force and not branchmerge
1644 overwrite = force and not branchmerge
1602 ### check phase
1645 ### check phase
1603 if not overwrite:
1646 if not overwrite:
1604 if len(pl) > 1:
1647 if len(pl) > 1:
1605 raise error.Abort(_(b"outstanding uncommitted merge"))
1648 raise error.Abort(_(b"outstanding uncommitted merge"))
1606 ms = mergestatemod.mergestate.read(repo)
1649 ms = mergestatemod.mergestate.read(repo)
1607 if list(ms.unresolved()):
1650 if list(ms.unresolved()):
1608 raise error.Abort(
1651 raise error.Abort(
1609 _(b"outstanding merge conflicts"),
1652 _(b"outstanding merge conflicts"),
1610 hint=_(b"use 'hg resolve' to resolve"),
1653 hint=_(b"use 'hg resolve' to resolve"),
1611 )
1654 )
1612 if branchmerge:
1655 if branchmerge:
1613 if pas == [p2]:
1656 if pas == [p2]:
1614 raise error.Abort(
1657 raise error.Abort(
1615 _(
1658 _(
1616 b"merging with a working directory ancestor"
1659 b"merging with a working directory ancestor"
1617 b" has no effect"
1660 b" has no effect"
1618 )
1661 )
1619 )
1662 )
1620 elif pas == [p1]:
1663 elif pas == [p1]:
1621 if not mergeancestor and wc.branch() == p2.branch():
1664 if not mergeancestor and wc.branch() == p2.branch():
1622 raise error.Abort(
1665 raise error.Abort(
1623 _(b"nothing to merge"),
1666 _(b"nothing to merge"),
1624 hint=_(b"use 'hg update' or check 'hg heads'"),
1667 hint=_(b"use 'hg update' or check 'hg heads'"),
1625 )
1668 )
1626 if not force and (wc.files() or wc.deleted()):
1669 if not force and (wc.files() or wc.deleted()):
1627 raise error.Abort(
1670 raise error.Abort(
1628 _(b"uncommitted changes"),
1671 _(b"uncommitted changes"),
1629 hint=_(b"use 'hg status' to list changes"),
1672 hint=_(b"use 'hg status' to list changes"),
1630 )
1673 )
1631 if not wc.isinmemory():
1674 if not wc.isinmemory():
1632 for s in sorted(wc.substate):
1675 for s in sorted(wc.substate):
1633 wc.sub(s).bailifchanged()
1676 wc.sub(s).bailifchanged()
1634
1677
1635 elif not overwrite:
1678 elif not overwrite:
1636 if p1 == p2: # no-op update
1679 if p1 == p2: # no-op update
1637 # call the hooks and exit early
1680 # call the hooks and exit early
1638 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1681 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1639 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1682 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1640 return updateresult(0, 0, 0, 0)
1683 return updateresult(0, 0, 0, 0)
1641
1684
1642 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1685 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1643 [p1],
1686 [p1],
1644 [p2],
1687 [p2],
1645 ): # nonlinear
1688 ): # nonlinear
1646 dirty = wc.dirty(missing=True)
1689 dirty = wc.dirty(missing=True)
1647 if dirty:
1690 if dirty:
1648 # Branching is a bit strange to ensure we do the minimal
1691 # Branching is a bit strange to ensure we do the minimal
1649 # amount of call to obsutil.foreground.
1692 # amount of call to obsutil.foreground.
1650 foreground = obsutil.foreground(repo, [p1.node()])
1693 foreground = obsutil.foreground(repo, [p1.node()])
1651 # note: the <node> variable contains a random identifier
1694 # note: the <node> variable contains a random identifier
1652 if repo[node].node() in foreground:
1695 if repo[node].node() in foreground:
1653 pass # allow updating to successors
1696 pass # allow updating to successors
1654 else:
1697 else:
1655 msg = _(b"uncommitted changes")
1698 msg = _(b"uncommitted changes")
1656 hint = _(b"commit or update --clean to discard changes")
1699 hint = _(b"commit or update --clean to discard changes")
1657 raise error.UpdateAbort(msg, hint=hint)
1700 raise error.UpdateAbort(msg, hint=hint)
1658 else:
1701 else:
1659 # Allow jumping branches if clean and specific rev given
1702 # Allow jumping branches if clean and specific rev given
1660 pass
1703 pass
1661
1704
1662 if overwrite:
1705 if overwrite:
1663 pas = [wc]
1706 pas = [wc]
1664 elif not branchmerge:
1707 elif not branchmerge:
1665 pas = [p1]
1708 pas = [p1]
1666
1709
1667 # deprecated config: merge.followcopies
1710 # deprecated config: merge.followcopies
1668 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1711 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1669 if overwrite:
1712 if overwrite:
1670 followcopies = False
1713 followcopies = False
1671 elif not pas[0]:
1714 elif not pas[0]:
1672 followcopies = False
1715 followcopies = False
1673 if not branchmerge and not wc.dirty(missing=True):
1716 if not branchmerge and not wc.dirty(missing=True):
1674 followcopies = False
1717 followcopies = False
1675
1718
1676 ### calculate phase
1719 ### calculate phase
1677 actionbyfile, diverge, renamedelete = calculateupdates(
1720 actionbyfile, diverge, renamedelete = calculateupdates(
1678 repo,
1721 repo,
1679 wc,
1722 wc,
1680 p2,
1723 p2,
1681 pas,
1724 pas,
1682 branchmerge,
1725 branchmerge,
1683 force,
1726 force,
1684 mergeancestor,
1727 mergeancestor,
1685 followcopies,
1728 followcopies,
1686 matcher=matcher,
1729 matcher=matcher,
1687 mergeforce=mergeforce,
1730 mergeforce=mergeforce,
1688 )
1731 )
1689
1732
1690 if updatecheck == UPDATECHECK_NO_CONFLICT:
1733 if updatecheck == UPDATECHECK_NO_CONFLICT:
1691 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1734 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1692 if m not in (
1735 if m not in (
1693 mergestatemod.ACTION_GET,
1736 mergestatemod.ACTION_GET,
1694 mergestatemod.ACTION_KEEP,
1737 mergestatemod.ACTION_KEEP,
1695 mergestatemod.ACTION_EXEC,
1738 mergestatemod.ACTION_EXEC,
1696 mergestatemod.ACTION_REMOVE,
1739 mergestatemod.ACTION_REMOVE,
1697 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1740 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1698 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1741 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1699 ):
1742 ):
1700 msg = _(b"conflicting changes")
1743 msg = _(b"conflicting changes")
1701 hint = _(b"commit or update --clean to discard changes")
1744 hint = _(b"commit or update --clean to discard changes")
1702 raise error.Abort(msg, hint=hint)
1745 raise error.Abort(msg, hint=hint)
1703
1746
1704 # Prompt and create actions. Most of this is in the resolve phase
1747 # Prompt and create actions. Most of this is in the resolve phase
1705 # already, but we can't handle .hgsubstate in filemerge or
1748 # already, but we can't handle .hgsubstate in filemerge or
1706 # subrepoutil.submerge yet so we have to keep prompting for it.
1749 # subrepoutil.submerge yet so we have to keep prompting for it.
1707 if b'.hgsubstate' in actionbyfile:
1750 if b'.hgsubstate' in actionbyfile:
1708 f = b'.hgsubstate'
1751 f = b'.hgsubstate'
1709 m, args, msg = actionbyfile[f]
1752 m, args, msg = actionbyfile[f]
1710 prompts = filemerge.partextras(labels)
1753 prompts = filemerge.partextras(labels)
1711 prompts[b'f'] = f
1754 prompts[b'f'] = f
1712 if m == mergestatemod.ACTION_CHANGED_DELETED:
1755 if m == mergestatemod.ACTION_CHANGED_DELETED:
1713 if repo.ui.promptchoice(
1756 if repo.ui.promptchoice(
1714 _(
1757 _(
1715 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1758 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1716 b"use (c)hanged version or (d)elete?"
1759 b"use (c)hanged version or (d)elete?"
1717 b"$$ &Changed $$ &Delete"
1760 b"$$ &Changed $$ &Delete"
1718 )
1761 )
1719 % prompts,
1762 % prompts,
1720 0,
1763 0,
1721 ):
1764 ):
1722 actionbyfile[f] = (
1765 actionbyfile[f] = (
1723 mergestatemod.ACTION_REMOVE,
1766 mergestatemod.ACTION_REMOVE,
1724 None,
1767 None,
1725 b'prompt delete',
1768 b'prompt delete',
1726 )
1769 )
1727 elif f in p1:
1770 elif f in p1:
1728 actionbyfile[f] = (
1771 actionbyfile[f] = (
1729 mergestatemod.ACTION_ADD_MODIFIED,
1772 mergestatemod.ACTION_ADD_MODIFIED,
1730 None,
1773 None,
1731 b'prompt keep',
1774 b'prompt keep',
1732 )
1775 )
1733 else:
1776 else:
1734 actionbyfile[f] = (
1777 actionbyfile[f] = (
1735 mergestatemod.ACTION_ADD,
1778 mergestatemod.ACTION_ADD,
1736 None,
1779 None,
1737 b'prompt keep',
1780 b'prompt keep',
1738 )
1781 )
1739 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1782 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1740 f1, f2, fa, move, anc = args
1783 f1, f2, fa, move, anc = args
1741 flags = p2[f2].flags()
1784 flags = p2[f2].flags()
1742 if (
1785 if (
1743 repo.ui.promptchoice(
1786 repo.ui.promptchoice(
1744 _(
1787 _(
1745 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1788 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1746 b"use (c)hanged version or leave (d)eleted?"
1789 b"use (c)hanged version or leave (d)eleted?"
1747 b"$$ &Changed $$ &Deleted"
1790 b"$$ &Changed $$ &Deleted"
1748 )
1791 )
1749 % prompts,
1792 % prompts,
1750 0,
1793 0,
1751 )
1794 )
1752 == 0
1795 == 0
1753 ):
1796 ):
1754 actionbyfile[f] = (
1797 actionbyfile[f] = (
1755 mergestatemod.ACTION_GET,
1798 mergestatemod.ACTION_GET,
1756 (flags, False),
1799 (flags, False),
1757 b'prompt recreating',
1800 b'prompt recreating',
1758 )
1801 )
1759 else:
1802 else:
1760 del actionbyfile[f]
1803 del actionbyfile[f]
1761
1804
1762 # Convert to dictionary-of-lists format
1805 # Convert to dictionary-of-lists format
1763 actions = emptyactions()
1806 actions = emptyactions()
1764 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1807 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1765 if m not in actions:
1808 if m not in actions:
1766 actions[m] = []
1809 actions[m] = []
1767 actions[m].append((f, args, msg))
1810 actions[m].append((f, args, msg))
1768
1811
1769 # ACTION_GET_OTHER_AND_STORE is a mergestatemod.ACTION_GET + store in mergestate
1812 # ACTION_GET_OTHER_AND_STORE is a mergestatemod.ACTION_GET + store in mergestate
1770 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1813 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1771 actions[mergestatemod.ACTION_GET].append(e)
1814 actions[mergestatemod.ACTION_GET].append(e)
1772
1815
1773 if not util.fscasesensitive(repo.path):
1816 if not util.fscasesensitive(repo.path):
1774 # check collision between files only in p2 for clean update
1817 # check collision between files only in p2 for clean update
1775 if not branchmerge and (
1818 if not branchmerge and (
1776 force or not wc.dirty(missing=True, branch=False)
1819 force or not wc.dirty(missing=True, branch=False)
1777 ):
1820 ):
1778 _checkcollision(repo, p2.manifest(), None)
1821 _checkcollision(repo, p2.manifest(), None)
1779 else:
1822 else:
1780 _checkcollision(repo, wc.manifest(), actions)
1823 _checkcollision(repo, wc.manifest(), actions)
1781
1824
1782 # divergent renames
1825 # divergent renames
1783 for f, fl in sorted(pycompat.iteritems(diverge)):
1826 for f, fl in sorted(pycompat.iteritems(diverge)):
1784 repo.ui.warn(
1827 repo.ui.warn(
1785 _(
1828 _(
1786 b"note: possible conflict - %s was renamed "
1829 b"note: possible conflict - %s was renamed "
1787 b"multiple times to:\n"
1830 b"multiple times to:\n"
1788 )
1831 )
1789 % f
1832 % f
1790 )
1833 )
1791 for nf in sorted(fl):
1834 for nf in sorted(fl):
1792 repo.ui.warn(b" %s\n" % nf)
1835 repo.ui.warn(b" %s\n" % nf)
1793
1836
1794 # rename and delete
1837 # rename and delete
1795 for f, fl in sorted(pycompat.iteritems(renamedelete)):
1838 for f, fl in sorted(pycompat.iteritems(renamedelete)):
1796 repo.ui.warn(
1839 repo.ui.warn(
1797 _(
1840 _(
1798 b"note: possible conflict - %s was deleted "
1841 b"note: possible conflict - %s was deleted "
1799 b"and renamed to:\n"
1842 b"and renamed to:\n"
1800 )
1843 )
1801 % f
1844 % f
1802 )
1845 )
1803 for nf in sorted(fl):
1846 for nf in sorted(fl):
1804 repo.ui.warn(b" %s\n" % nf)
1847 repo.ui.warn(b" %s\n" % nf)
1805
1848
1806 ### apply phase
1849 ### apply phase
1807 if not branchmerge: # just jump to the new rev
1850 if not branchmerge: # just jump to the new rev
1808 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1851 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1809 # If we're doing a partial update, we need to skip updating
1852 # If we're doing a partial update, we need to skip updating
1810 # the dirstate.
1853 # the dirstate.
1811 always = matcher is None or matcher.always()
1854 always = matcher is None or matcher.always()
1812 updatedirstate = updatedirstate and always and not wc.isinmemory()
1855 updatedirstate = updatedirstate and always and not wc.isinmemory()
1813 if updatedirstate:
1856 if updatedirstate:
1814 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
1857 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
1815 # note that we're in the middle of an update
1858 # note that we're in the middle of an update
1816 repo.vfs.write(b'updatestate', p2.hex())
1859 repo.vfs.write(b'updatestate', p2.hex())
1817
1860
1818 # Advertise fsmonitor when its presence could be useful.
1861 _advertisefsmonitor(
1819 #
1862 repo, len(actions[mergestatemod.ACTION_GET]), p1.node()
1820 # We only advertise when performing an update from an empty working
1821 # directory. This typically only occurs during initial clone.
1822 #
1823 # We give users a mechanism to disable the warning in case it is
1824 # annoying.
1825 #
1826 # We only allow on Linux and MacOS because that's where fsmonitor is
1827 # considered stable.
1828 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1829 fsmonitorthreshold = repo.ui.configint(
1830 b'fsmonitor', b'warn_update_file_count'
1831 )
1863 )
1832 try:
1833 # avoid cycle: extensions -> cmdutil -> merge
1834 from . import extensions
1835
1836 extensions.find(b'fsmonitor')
1837 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1838 # We intentionally don't look at whether fsmonitor has disabled
1839 # itself because a) fsmonitor may have already printed a warning
1840 # b) we only care about the config state here.
1841 except KeyError:
1842 fsmonitorenabled = False
1843
1844 if (
1845 fsmonitorwarning
1846 and not fsmonitorenabled
1847 and p1.node() == nullid
1848 and len(actions[mergestatemod.ACTION_GET]) >= fsmonitorthreshold
1849 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1850 ):
1851 repo.ui.warn(
1852 _(
1853 b'(warning: large working directory being used without '
1854 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1855 b'see "hg help -e fsmonitor")\n'
1856 )
1857 )
1858
1864
1859 wantfiledata = updatedirstate and not branchmerge
1865 wantfiledata = updatedirstate and not branchmerge
1860 stats, getfiledata = applyupdates(
1866 stats, getfiledata = applyupdates(
1861 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
1867 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
1862 )
1868 )
1863
1869
1864 if updatedirstate:
1870 if updatedirstate:
1865 with repo.dirstate.parentchange():
1871 with repo.dirstate.parentchange():
1866 repo.setparents(fp1, fp2)
1872 repo.setparents(fp1, fp2)
1867 mergestatemod.recordupdates(
1873 mergestatemod.recordupdates(
1868 repo, actions, branchmerge, getfiledata
1874 repo, actions, branchmerge, getfiledata
1869 )
1875 )
1870 # update completed, clear state
1876 # update completed, clear state
1871 util.unlink(repo.vfs.join(b'updatestate'))
1877 util.unlink(repo.vfs.join(b'updatestate'))
1872
1878
1873 if not branchmerge:
1879 if not branchmerge:
1874 repo.dirstate.setbranch(p2.branch())
1880 repo.dirstate.setbranch(p2.branch())
1875
1881
1876 # If we're updating to a location, clean up any stale temporary includes
1882 # If we're updating to a location, clean up any stale temporary includes
1877 # (ex: this happens during hg rebase --abort).
1883 # (ex: this happens during hg rebase --abort).
1878 if not branchmerge:
1884 if not branchmerge:
1879 sparse.prunetemporaryincludes(repo)
1885 sparse.prunetemporaryincludes(repo)
1880
1886
1881 if updatedirstate:
1887 if updatedirstate:
1882 repo.hook(
1888 repo.hook(
1883 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
1889 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
1884 )
1890 )
1885 return stats
1891 return stats
1886
1892
1887
1893
1888 def merge(ctx, labels=None, force=False, wc=None):
1894 def merge(ctx, labels=None, force=False, wc=None):
1889 """Merge another topological branch into the working copy.
1895 """Merge another topological branch into the working copy.
1890
1896
1891 force = whether the merge was run with 'merge --force' (deprecated)
1897 force = whether the merge was run with 'merge --force' (deprecated)
1892 """
1898 """
1893
1899
1894 return update(
1900 return update(
1895 ctx.repo(),
1901 ctx.repo(),
1896 ctx.rev(),
1902 ctx.rev(),
1897 labels=labels,
1903 labels=labels,
1898 branchmerge=True,
1904 branchmerge=True,
1899 force=force,
1905 force=force,
1900 mergeforce=force,
1906 mergeforce=force,
1901 wc=wc,
1907 wc=wc,
1902 )
1908 )
1903
1909
1904
1910
1905 def clean_update(ctx, wc=None):
1911 def clean_update(ctx, wc=None):
1906 """Do a clean update to the given commit.
1912 """Do a clean update to the given commit.
1907
1913
1908 This involves updating to the commit and discarding any changes in the
1914 This involves updating to the commit and discarding any changes in the
1909 working copy.
1915 working copy.
1910 """
1916 """
1911 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
1917 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
1912
1918
1913
1919
1914 def revert_to(ctx, matcher=None, wc=None):
1920 def revert_to(ctx, matcher=None, wc=None):
1915 """Revert the working copy to the given commit.
1921 """Revert the working copy to the given commit.
1916
1922
1917 The working copy will keep its current parent(s) but its content will
1923 The working copy will keep its current parent(s) but its content will
1918 be the same as in the given commit.
1924 be the same as in the given commit.
1919 """
1925 """
1920
1926
1921 return update(
1927 return update(
1922 ctx.repo(),
1928 ctx.repo(),
1923 ctx.rev(),
1929 ctx.rev(),
1924 branchmerge=False,
1930 branchmerge=False,
1925 force=True,
1931 force=True,
1926 updatedirstate=False,
1932 updatedirstate=False,
1927 matcher=matcher,
1933 matcher=matcher,
1928 wc=wc,
1934 wc=wc,
1929 )
1935 )
1930
1936
1931
1937
1932 def graft(
1938 def graft(
1933 repo,
1939 repo,
1934 ctx,
1940 ctx,
1935 base=None,
1941 base=None,
1936 labels=None,
1942 labels=None,
1937 keepparent=False,
1943 keepparent=False,
1938 keepconflictparent=False,
1944 keepconflictparent=False,
1939 wctx=None,
1945 wctx=None,
1940 ):
1946 ):
1941 """Do a graft-like merge.
1947 """Do a graft-like merge.
1942
1948
1943 This is a merge where the merge ancestor is chosen such that one
1949 This is a merge where the merge ancestor is chosen such that one
1944 or more changesets are grafted onto the current changeset. In
1950 or more changesets are grafted onto the current changeset. In
1945 addition to the merge, this fixes up the dirstate to include only
1951 addition to the merge, this fixes up the dirstate to include only
1946 a single parent (if keepparent is False) and tries to duplicate any
1952 a single parent (if keepparent is False) and tries to duplicate any
1947 renames/copies appropriately.
1953 renames/copies appropriately.
1948
1954
1949 ctx - changeset to rebase
1955 ctx - changeset to rebase
1950 base - merge base, or ctx.p1() if not specified
1956 base - merge base, or ctx.p1() if not specified
1951 labels - merge labels eg ['local', 'graft']
1957 labels - merge labels eg ['local', 'graft']
1952 keepparent - keep second parent if any
1958 keepparent - keep second parent if any
1953 keepconflictparent - if unresolved, keep parent used for the merge
1959 keepconflictparent - if unresolved, keep parent used for the merge
1954
1960
1955 """
1961 """
1956 # If we're grafting a descendant onto an ancestor, be sure to pass
1962 # If we're grafting a descendant onto an ancestor, be sure to pass
1957 # mergeancestor=True to update. This does two things: 1) allows the merge if
1963 # mergeancestor=True to update. This does two things: 1) allows the merge if
1958 # the destination is the same as the parent of the ctx (so we can use graft
1964 # the destination is the same as the parent of the ctx (so we can use graft
1959 # to copy commits), and 2) informs update that the incoming changes are
1965 # to copy commits), and 2) informs update that the incoming changes are
1960 # newer than the destination so it doesn't prompt about "remote changed foo
1966 # newer than the destination so it doesn't prompt about "remote changed foo
1961 # which local deleted".
1967 # which local deleted".
1962 # We also pass mergeancestor=True when base is the same revision as p1. 2)
1968 # We also pass mergeancestor=True when base is the same revision as p1. 2)
1963 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
1969 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
1964 wctx = wctx or repo[None]
1970 wctx = wctx or repo[None]
1965 pctx = wctx.p1()
1971 pctx = wctx.p1()
1966 base = base or ctx.p1()
1972 base = base or ctx.p1()
1967 mergeancestor = (
1973 mergeancestor = (
1968 repo.changelog.isancestor(pctx.node(), ctx.node())
1974 repo.changelog.isancestor(pctx.node(), ctx.node())
1969 or pctx.rev() == base.rev()
1975 or pctx.rev() == base.rev()
1970 )
1976 )
1971
1977
1972 stats = update(
1978 stats = update(
1973 repo,
1979 repo,
1974 ctx.node(),
1980 ctx.node(),
1975 True,
1981 True,
1976 True,
1982 True,
1977 base.node(),
1983 base.node(),
1978 mergeancestor=mergeancestor,
1984 mergeancestor=mergeancestor,
1979 labels=labels,
1985 labels=labels,
1980 wc=wctx,
1986 wc=wctx,
1981 )
1987 )
1982
1988
1983 if keepconflictparent and stats.unresolvedcount:
1989 if keepconflictparent and stats.unresolvedcount:
1984 pother = ctx.node()
1990 pother = ctx.node()
1985 else:
1991 else:
1986 pother = nullid
1992 pother = nullid
1987 parents = ctx.parents()
1993 parents = ctx.parents()
1988 if keepparent and len(parents) == 2 and base in parents:
1994 if keepparent and len(parents) == 2 and base in parents:
1989 parents.remove(base)
1995 parents.remove(base)
1990 pother = parents[0].node()
1996 pother = parents[0].node()
1991 # Never set both parents equal to each other
1997 # Never set both parents equal to each other
1992 if pother == pctx.node():
1998 if pother == pctx.node():
1993 pother = nullid
1999 pother = nullid
1994
2000
1995 if wctx.isinmemory():
2001 if wctx.isinmemory():
1996 wctx.setparents(pctx.node(), pother)
2002 wctx.setparents(pctx.node(), pother)
1997 # fix up dirstate for copies and renames
2003 # fix up dirstate for copies and renames
1998 copies.graftcopies(wctx, ctx, base)
2004 copies.graftcopies(wctx, ctx, base)
1999 else:
2005 else:
2000 with repo.dirstate.parentchange():
2006 with repo.dirstate.parentchange():
2001 repo.setparents(pctx.node(), pother)
2007 repo.setparents(pctx.node(), pother)
2002 repo.dirstate.write(repo.currenttransaction())
2008 repo.dirstate.write(repo.currenttransaction())
2003 # fix up dirstate for copies and renames
2009 # fix up dirstate for copies and renames
2004 copies.graftcopies(wctx, ctx, base)
2010 copies.graftcopies(wctx, ctx, base)
2005 return stats
2011 return stats
2006
2012
2007
2013
2008 def purge(
2014 def purge(
2009 repo,
2015 repo,
2010 matcher,
2016 matcher,
2011 unknown=True,
2017 unknown=True,
2012 ignored=False,
2018 ignored=False,
2013 removeemptydirs=True,
2019 removeemptydirs=True,
2014 removefiles=True,
2020 removefiles=True,
2015 abortonerror=False,
2021 abortonerror=False,
2016 noop=False,
2022 noop=False,
2017 ):
2023 ):
2018 """Purge the working directory of untracked files.
2024 """Purge the working directory of untracked files.
2019
2025
2020 ``matcher`` is a matcher configured to scan the working directory -
2026 ``matcher`` is a matcher configured to scan the working directory -
2021 potentially a subset.
2027 potentially a subset.
2022
2028
2023 ``unknown`` controls whether unknown files should be purged.
2029 ``unknown`` controls whether unknown files should be purged.
2024
2030
2025 ``ignored`` controls whether ignored files should be purged.
2031 ``ignored`` controls whether ignored files should be purged.
2026
2032
2027 ``removeemptydirs`` controls whether empty directories should be removed.
2033 ``removeemptydirs`` controls whether empty directories should be removed.
2028
2034
2029 ``removefiles`` controls whether files are removed.
2035 ``removefiles`` controls whether files are removed.
2030
2036
2031 ``abortonerror`` causes an exception to be raised if an error occurs
2037 ``abortonerror`` causes an exception to be raised if an error occurs
2032 deleting a file or directory.
2038 deleting a file or directory.
2033
2039
2034 ``noop`` controls whether to actually remove files. If not defined, actions
2040 ``noop`` controls whether to actually remove files. If not defined, actions
2035 will be taken.
2041 will be taken.
2036
2042
2037 Returns an iterable of relative paths in the working directory that were
2043 Returns an iterable of relative paths in the working directory that were
2038 or would be removed.
2044 or would be removed.
2039 """
2045 """
2040
2046
2041 def remove(removefn, path):
2047 def remove(removefn, path):
2042 try:
2048 try:
2043 removefn(path)
2049 removefn(path)
2044 except OSError:
2050 except OSError:
2045 m = _(b'%s cannot be removed') % path
2051 m = _(b'%s cannot be removed') % path
2046 if abortonerror:
2052 if abortonerror:
2047 raise error.Abort(m)
2053 raise error.Abort(m)
2048 else:
2054 else:
2049 repo.ui.warn(_(b'warning: %s\n') % m)
2055 repo.ui.warn(_(b'warning: %s\n') % m)
2050
2056
2051 # There's no API to copy a matcher. So mutate the passed matcher and
2057 # There's no API to copy a matcher. So mutate the passed matcher and
2052 # restore it when we're done.
2058 # restore it when we're done.
2053 oldtraversedir = matcher.traversedir
2059 oldtraversedir = matcher.traversedir
2054
2060
2055 res = []
2061 res = []
2056
2062
2057 try:
2063 try:
2058 if removeemptydirs:
2064 if removeemptydirs:
2059 directories = []
2065 directories = []
2060 matcher.traversedir = directories.append
2066 matcher.traversedir = directories.append
2061
2067
2062 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2068 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2063
2069
2064 if removefiles:
2070 if removefiles:
2065 for f in sorted(status.unknown + status.ignored):
2071 for f in sorted(status.unknown + status.ignored):
2066 if not noop:
2072 if not noop:
2067 repo.ui.note(_(b'removing file %s\n') % f)
2073 repo.ui.note(_(b'removing file %s\n') % f)
2068 remove(repo.wvfs.unlink, f)
2074 remove(repo.wvfs.unlink, f)
2069 res.append(f)
2075 res.append(f)
2070
2076
2071 if removeemptydirs:
2077 if removeemptydirs:
2072 for f in sorted(directories, reverse=True):
2078 for f in sorted(directories, reverse=True):
2073 if matcher(f) and not repo.wvfs.listdir(f):
2079 if matcher(f) and not repo.wvfs.listdir(f):
2074 if not noop:
2080 if not noop:
2075 repo.ui.note(_(b'removing directory %s\n') % f)
2081 repo.ui.note(_(b'removing directory %s\n') % f)
2076 remove(repo.wvfs.rmdir, f)
2082 remove(repo.wvfs.rmdir, f)
2077 res.append(f)
2083 res.append(f)
2078
2084
2079 return res
2085 return res
2080
2086
2081 finally:
2087 finally:
2082 matcher.traversedir = oldtraversedir
2088 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now