##// END OF EJS Templates
merge: don't grab wlock when merging in memory...
Martin von Zweigbergk -
r45536:d1471dbb default
parent child Browse files
Show More
@@ -1,2073 +1,2077
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 extra 'a' is added by working copy manifest to mark
728 # This extra 'a' is added by working copy manifest to mark
729 # the file as locally added. We should forget it instead of
729 # the file as locally added. We should forget it instead of
730 # deleting it.
730 # deleting it.
731 actions[f] = (
731 actions[f] = (
732 mergestatemod.ACTION_FORGET,
732 mergestatemod.ACTION_FORGET,
733 None,
733 None,
734 b'remote deleted',
734 b'remote deleted',
735 )
735 )
736 else:
736 else:
737 actions[f] = (
737 actions[f] = (
738 mergestatemod.ACTION_REMOVE,
738 mergestatemod.ACTION_REMOVE,
739 None,
739 None,
740 b'other deleted',
740 b'other deleted',
741 )
741 )
742 elif n2: # file exists only on remote side
742 elif n2: # file exists only on remote side
743 if f in copied1:
743 if f in copied1:
744 pass # we'll deal with it on m1 side
744 pass # we'll deal with it on m1 side
745 elif f in branch_copies2.movewithdir:
745 elif f in branch_copies2.movewithdir:
746 f2 = branch_copies2.movewithdir[f]
746 f2 = branch_copies2.movewithdir[f]
747 if f2 in m1:
747 if f2 in m1:
748 actions[f2] = (
748 actions[f2] = (
749 mergestatemod.ACTION_MERGE,
749 mergestatemod.ACTION_MERGE,
750 (f2, f, None, False, pa.node()),
750 (f2, f, None, False, pa.node()),
751 b'local directory rename, both created',
751 b'local directory rename, both created',
752 )
752 )
753 else:
753 else:
754 actions[f2] = (
754 actions[f2] = (
755 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
755 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
756 (f, fl2),
756 (f, fl2),
757 b'local directory rename - get from %s' % f,
757 b'local directory rename - get from %s' % f,
758 )
758 )
759 elif f in branch_copies2.copy:
759 elif f in branch_copies2.copy:
760 f2 = branch_copies2.copy[f]
760 f2 = branch_copies2.copy[f]
761 if f2 in m2:
761 if f2 in m2:
762 actions[f] = (
762 actions[f] = (
763 mergestatemod.ACTION_MERGE,
763 mergestatemod.ACTION_MERGE,
764 (f2, f, f2, False, pa.node()),
764 (f2, f, f2, False, pa.node()),
765 b'remote copied from %s' % f2,
765 b'remote copied from %s' % f2,
766 )
766 )
767 else:
767 else:
768 actions[f] = (
768 actions[f] = (
769 mergestatemod.ACTION_MERGE,
769 mergestatemod.ACTION_MERGE,
770 (f2, f, f2, True, pa.node()),
770 (f2, f, f2, True, pa.node()),
771 b'remote moved from %s' % f2,
771 b'remote moved from %s' % f2,
772 )
772 )
773 elif f not in ma:
773 elif f not in ma:
774 # local unknown, remote created: the logic is described by the
774 # local unknown, remote created: the logic is described by the
775 # following table:
775 # following table:
776 #
776 #
777 # force branchmerge different | action
777 # force branchmerge different | action
778 # n * * | create
778 # n * * | create
779 # y n * | create
779 # y n * | create
780 # y y n | create
780 # y y n | create
781 # y y y | merge
781 # y y y | merge
782 #
782 #
783 # Checking whether the files are different is expensive, so we
783 # Checking whether the files are different is expensive, so we
784 # don't do that when we can avoid it.
784 # don't do that when we can avoid it.
785 if not force:
785 if not force:
786 actions[f] = (
786 actions[f] = (
787 mergestatemod.ACTION_CREATED,
787 mergestatemod.ACTION_CREATED,
788 (fl2,),
788 (fl2,),
789 b'remote created',
789 b'remote created',
790 )
790 )
791 elif not branchmerge:
791 elif not branchmerge:
792 actions[f] = (
792 actions[f] = (
793 mergestatemod.ACTION_CREATED,
793 mergestatemod.ACTION_CREATED,
794 (fl2,),
794 (fl2,),
795 b'remote created',
795 b'remote created',
796 )
796 )
797 else:
797 else:
798 actions[f] = (
798 actions[f] = (
799 mergestatemod.ACTION_CREATED_MERGE,
799 mergestatemod.ACTION_CREATED_MERGE,
800 (fl2, pa.node()),
800 (fl2, pa.node()),
801 b'remote created, get or merge',
801 b'remote created, get or merge',
802 )
802 )
803 elif n2 != ma[f]:
803 elif n2 != ma[f]:
804 df = None
804 df = None
805 for d in branch_copies1.dirmove:
805 for d in branch_copies1.dirmove:
806 if f.startswith(d):
806 if f.startswith(d):
807 # new file added in a directory that was moved
807 # new file added in a directory that was moved
808 df = branch_copies1.dirmove[d] + f[len(d) :]
808 df = branch_copies1.dirmove[d] + f[len(d) :]
809 break
809 break
810 if df is not None and df in m1:
810 if df is not None and df in m1:
811 actions[df] = (
811 actions[df] = (
812 mergestatemod.ACTION_MERGE,
812 mergestatemod.ACTION_MERGE,
813 (df, f, f, False, pa.node()),
813 (df, f, f, False, pa.node()),
814 b'local directory rename - respect move '
814 b'local directory rename - respect move '
815 b'from %s' % f,
815 b'from %s' % f,
816 )
816 )
817 elif acceptremote:
817 elif acceptremote:
818 actions[f] = (
818 actions[f] = (
819 mergestatemod.ACTION_CREATED,
819 mergestatemod.ACTION_CREATED,
820 (fl2,),
820 (fl2,),
821 b'remote recreating',
821 b'remote recreating',
822 )
822 )
823 else:
823 else:
824 actions[f] = (
824 actions[f] = (
825 mergestatemod.ACTION_DELETED_CHANGED,
825 mergestatemod.ACTION_DELETED_CHANGED,
826 (None, f, f, False, pa.node()),
826 (None, f, f, False, pa.node()),
827 b'prompt deleted/changed',
827 b'prompt deleted/changed',
828 )
828 )
829
829
830 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
830 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
831 # If we are merging, look for path conflicts.
831 # If we are merging, look for path conflicts.
832 checkpathconflicts(repo, wctx, p2, actions)
832 checkpathconflicts(repo, wctx, p2, actions)
833
833
834 narrowmatch = repo.narrowmatch()
834 narrowmatch = repo.narrowmatch()
835 if not narrowmatch.always():
835 if not narrowmatch.always():
836 # Updates "actions" in place
836 # Updates "actions" in place
837 _filternarrowactions(narrowmatch, branchmerge, actions)
837 _filternarrowactions(narrowmatch, branchmerge, actions)
838
838
839 renamedelete = branch_copies1.renamedelete
839 renamedelete = branch_copies1.renamedelete
840 renamedelete.update(branch_copies2.renamedelete)
840 renamedelete.update(branch_copies2.renamedelete)
841
841
842 return actions, diverge, renamedelete
842 return actions, diverge, renamedelete
843
843
844
844
845 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
845 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
846 """Resolves false conflicts where the nodeid changed but the content
846 """Resolves false conflicts where the nodeid changed but the content
847 remained the same."""
847 remained the same."""
848 # We force a copy of actions.items() because we're going to mutate
848 # We force a copy of actions.items() because we're going to mutate
849 # actions as we resolve trivial conflicts.
849 # actions as we resolve trivial conflicts.
850 for f, (m, args, msg) in list(actions.items()):
850 for f, (m, args, msg) in list(actions.items()):
851 if (
851 if (
852 m == mergestatemod.ACTION_CHANGED_DELETED
852 m == mergestatemod.ACTION_CHANGED_DELETED
853 and f in ancestor
853 and f in ancestor
854 and not wctx[f].cmp(ancestor[f])
854 and not wctx[f].cmp(ancestor[f])
855 ):
855 ):
856 # local did change but ended up with same content
856 # local did change but ended up with same content
857 actions[f] = mergestatemod.ACTION_REMOVE, None, b'prompt same'
857 actions[f] = mergestatemod.ACTION_REMOVE, None, b'prompt same'
858 elif (
858 elif (
859 m == mergestatemod.ACTION_DELETED_CHANGED
859 m == mergestatemod.ACTION_DELETED_CHANGED
860 and f in ancestor
860 and f in ancestor
861 and not mctx[f].cmp(ancestor[f])
861 and not mctx[f].cmp(ancestor[f])
862 ):
862 ):
863 # remote did change but ended up with same content
863 # remote did change but ended up with same content
864 del actions[f] # don't get = keep local deleted
864 del actions[f] # don't get = keep local deleted
865
865
866
866
867 def calculateupdates(
867 def calculateupdates(
868 repo,
868 repo,
869 wctx,
869 wctx,
870 mctx,
870 mctx,
871 ancestors,
871 ancestors,
872 branchmerge,
872 branchmerge,
873 force,
873 force,
874 acceptremote,
874 acceptremote,
875 followcopies,
875 followcopies,
876 matcher=None,
876 matcher=None,
877 mergeforce=False,
877 mergeforce=False,
878 ):
878 ):
879 """Calculate the actions needed to merge mctx into wctx using ancestors"""
879 """Calculate the actions needed to merge mctx into wctx using ancestors"""
880 # Avoid cycle.
880 # Avoid cycle.
881 from . import sparse
881 from . import sparse
882
882
883 if len(ancestors) == 1: # default
883 if len(ancestors) == 1: # default
884 actions, diverge, renamedelete = manifestmerge(
884 actions, diverge, renamedelete = manifestmerge(
885 repo,
885 repo,
886 wctx,
886 wctx,
887 mctx,
887 mctx,
888 ancestors[0],
888 ancestors[0],
889 branchmerge,
889 branchmerge,
890 force,
890 force,
891 matcher,
891 matcher,
892 acceptremote,
892 acceptremote,
893 followcopies,
893 followcopies,
894 )
894 )
895 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
895 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
896
896
897 else: # only when merge.preferancestor=* - the default
897 else: # only when merge.preferancestor=* - the default
898 repo.ui.note(
898 repo.ui.note(
899 _(b"note: merging %s and %s using bids from ancestors %s\n")
899 _(b"note: merging %s and %s using bids from ancestors %s\n")
900 % (
900 % (
901 wctx,
901 wctx,
902 mctx,
902 mctx,
903 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
903 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
904 )
904 )
905 )
905 )
906
906
907 # Call for bids
907 # Call for bids
908 fbids = (
908 fbids = (
909 {}
909 {}
910 ) # mapping filename to bids (action method to list af actions)
910 ) # mapping filename to bids (action method to list af actions)
911 diverge, renamedelete = None, None
911 diverge, renamedelete = None, None
912 for ancestor in ancestors:
912 for ancestor in ancestors:
913 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
913 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
914 actions, diverge1, renamedelete1 = manifestmerge(
914 actions, diverge1, renamedelete1 = manifestmerge(
915 repo,
915 repo,
916 wctx,
916 wctx,
917 mctx,
917 mctx,
918 ancestor,
918 ancestor,
919 branchmerge,
919 branchmerge,
920 force,
920 force,
921 matcher,
921 matcher,
922 acceptremote,
922 acceptremote,
923 followcopies,
923 followcopies,
924 forcefulldiff=True,
924 forcefulldiff=True,
925 )
925 )
926 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
926 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
927
927
928 # Track the shortest set of warning on the theory that bid
928 # Track the shortest set of warning on the theory that bid
929 # merge will correctly incorporate more information
929 # merge will correctly incorporate more information
930 if diverge is None or len(diverge1) < len(diverge):
930 if diverge is None or len(diverge1) < len(diverge):
931 diverge = diverge1
931 diverge = diverge1
932 if renamedelete is None or len(renamedelete) < len(renamedelete1):
932 if renamedelete is None or len(renamedelete) < len(renamedelete1):
933 renamedelete = renamedelete1
933 renamedelete = renamedelete1
934
934
935 for f, a in sorted(pycompat.iteritems(actions)):
935 for f, a in sorted(pycompat.iteritems(actions)):
936 m, args, msg = a
936 m, args, msg = a
937 if m == mergestatemod.ACTION_GET_OTHER_AND_STORE:
937 if m == mergestatemod.ACTION_GET_OTHER_AND_STORE:
938 m = mergestatemod.ACTION_GET
938 m = mergestatemod.ACTION_GET
939 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
939 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
940 if f in fbids:
940 if f in fbids:
941 d = fbids[f]
941 d = fbids[f]
942 if m in d:
942 if m in d:
943 d[m].append(a)
943 d[m].append(a)
944 else:
944 else:
945 d[m] = [a]
945 d[m] = [a]
946 else:
946 else:
947 fbids[f] = {m: [a]}
947 fbids[f] = {m: [a]}
948
948
949 # Pick the best bid for each file
949 # Pick the best bid for each file
950 repo.ui.note(_(b'\nauction for merging merge bids\n'))
950 repo.ui.note(_(b'\nauction for merging merge bids\n'))
951 actions = {}
951 actions = {}
952 for f, bids in sorted(fbids.items()):
952 for f, bids in sorted(fbids.items()):
953 # bids is a mapping from action method to list af actions
953 # bids is a mapping from action method to list af actions
954 # Consensus?
954 # Consensus?
955 if len(bids) == 1: # all bids are the same kind of method
955 if len(bids) == 1: # all bids are the same kind of method
956 m, l = list(bids.items())[0]
956 m, l = list(bids.items())[0]
957 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
957 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
958 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
958 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
959 actions[f] = l[0]
959 actions[f] = l[0]
960 continue
960 continue
961 # If keep is an option, just do it.
961 # If keep is an option, just do it.
962 if mergestatemod.ACTION_KEEP in bids:
962 if mergestatemod.ACTION_KEEP in bids:
963 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
963 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
964 actions[f] = bids[mergestatemod.ACTION_KEEP][0]
964 actions[f] = bids[mergestatemod.ACTION_KEEP][0]
965 continue
965 continue
966 # If there are gets and they all agree [how could they not?], do it.
966 # If there are gets and they all agree [how could they not?], do it.
967 if mergestatemod.ACTION_GET in bids:
967 if mergestatemod.ACTION_GET in bids:
968 ga0 = bids[mergestatemod.ACTION_GET][0]
968 ga0 = bids[mergestatemod.ACTION_GET][0]
969 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
969 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
970 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
970 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
971 actions[f] = ga0
971 actions[f] = ga0
972 continue
972 continue
973 # TODO: Consider other simple actions such as mode changes
973 # TODO: Consider other simple actions such as mode changes
974 # Handle inefficient democrazy.
974 # Handle inefficient democrazy.
975 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
975 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
976 for m, l in sorted(bids.items()):
976 for m, l in sorted(bids.items()):
977 for _f, args, msg in l:
977 for _f, args, msg in l:
978 repo.ui.note(b' %s -> %s\n' % (msg, m))
978 repo.ui.note(b' %s -> %s\n' % (msg, m))
979 # Pick random action. TODO: Instead, prompt user when resolving
979 # Pick random action. TODO: Instead, prompt user when resolving
980 m, l = list(bids.items())[0]
980 m, l = list(bids.items())[0]
981 repo.ui.warn(
981 repo.ui.warn(
982 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
982 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
983 )
983 )
984 actions[f] = l[0]
984 actions[f] = l[0]
985 continue
985 continue
986 repo.ui.note(_(b'end of auction\n\n'))
986 repo.ui.note(_(b'end of auction\n\n'))
987
987
988 if wctx.rev() is None:
988 if wctx.rev() is None:
989 fractions = _forgetremoved(wctx, mctx, branchmerge)
989 fractions = _forgetremoved(wctx, mctx, branchmerge)
990 actions.update(fractions)
990 actions.update(fractions)
991
991
992 prunedactions = sparse.filterupdatesactions(
992 prunedactions = sparse.filterupdatesactions(
993 repo, wctx, mctx, branchmerge, actions
993 repo, wctx, mctx, branchmerge, actions
994 )
994 )
995 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
995 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
996
996
997 return prunedactions, diverge, renamedelete
997 return prunedactions, diverge, renamedelete
998
998
999
999
1000 def _getcwd():
1000 def _getcwd():
1001 try:
1001 try:
1002 return encoding.getcwd()
1002 return encoding.getcwd()
1003 except OSError as err:
1003 except OSError as err:
1004 if err.errno == errno.ENOENT:
1004 if err.errno == errno.ENOENT:
1005 return None
1005 return None
1006 raise
1006 raise
1007
1007
1008
1008
1009 def batchremove(repo, wctx, actions):
1009 def batchremove(repo, wctx, actions):
1010 """apply removes to the working directory
1010 """apply removes to the working directory
1011
1011
1012 yields tuples for progress updates
1012 yields tuples for progress updates
1013 """
1013 """
1014 verbose = repo.ui.verbose
1014 verbose = repo.ui.verbose
1015 cwd = _getcwd()
1015 cwd = _getcwd()
1016 i = 0
1016 i = 0
1017 for f, args, msg in actions:
1017 for f, args, msg in actions:
1018 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1018 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1019 if verbose:
1019 if verbose:
1020 repo.ui.note(_(b"removing %s\n") % f)
1020 repo.ui.note(_(b"removing %s\n") % f)
1021 wctx[f].audit()
1021 wctx[f].audit()
1022 try:
1022 try:
1023 wctx[f].remove(ignoremissing=True)
1023 wctx[f].remove(ignoremissing=True)
1024 except OSError as inst:
1024 except OSError as inst:
1025 repo.ui.warn(
1025 repo.ui.warn(
1026 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1026 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1027 )
1027 )
1028 if i == 100:
1028 if i == 100:
1029 yield i, f
1029 yield i, f
1030 i = 0
1030 i = 0
1031 i += 1
1031 i += 1
1032 if i > 0:
1032 if i > 0:
1033 yield i, f
1033 yield i, f
1034
1034
1035 if cwd and not _getcwd():
1035 if cwd and not _getcwd():
1036 # cwd was removed in the course of removing files; print a helpful
1036 # cwd was removed in the course of removing files; print a helpful
1037 # warning.
1037 # warning.
1038 repo.ui.warn(
1038 repo.ui.warn(
1039 _(
1039 _(
1040 b"current directory was removed\n"
1040 b"current directory was removed\n"
1041 b"(consider changing to repo root: %s)\n"
1041 b"(consider changing to repo root: %s)\n"
1042 )
1042 )
1043 % repo.root
1043 % repo.root
1044 )
1044 )
1045
1045
1046
1046
1047 def batchget(repo, mctx, wctx, wantfiledata, actions):
1047 def batchget(repo, mctx, wctx, wantfiledata, actions):
1048 """apply gets to the working directory
1048 """apply gets to the working directory
1049
1049
1050 mctx is the context to get from
1050 mctx is the context to get from
1051
1051
1052 Yields arbitrarily many (False, tuple) for progress updates, followed by
1052 Yields arbitrarily many (False, tuple) for progress updates, followed by
1053 exactly one (True, filedata). When wantfiledata is false, filedata is an
1053 exactly one (True, filedata). When wantfiledata is false, filedata is an
1054 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1054 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1055 mtime) of the file f written for each action.
1055 mtime) of the file f written for each action.
1056 """
1056 """
1057 filedata = {}
1057 filedata = {}
1058 verbose = repo.ui.verbose
1058 verbose = repo.ui.verbose
1059 fctx = mctx.filectx
1059 fctx = mctx.filectx
1060 ui = repo.ui
1060 ui = repo.ui
1061 i = 0
1061 i = 0
1062 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1062 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1063 for f, (flags, backup), msg in actions:
1063 for f, (flags, backup), msg in actions:
1064 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1064 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1065 if verbose:
1065 if verbose:
1066 repo.ui.note(_(b"getting %s\n") % f)
1066 repo.ui.note(_(b"getting %s\n") % f)
1067
1067
1068 if backup:
1068 if backup:
1069 # If a file or directory exists with the same name, back that
1069 # If a file or directory exists with the same name, back that
1070 # up. Otherwise, look to see if there is a file that conflicts
1070 # up. Otherwise, look to see if there is a file that conflicts
1071 # with a directory this file is in, and if so, back that up.
1071 # with a directory this file is in, and if so, back that up.
1072 conflicting = f
1072 conflicting = f
1073 if not repo.wvfs.lexists(f):
1073 if not repo.wvfs.lexists(f):
1074 for p in pathutil.finddirs(f):
1074 for p in pathutil.finddirs(f):
1075 if repo.wvfs.isfileorlink(p):
1075 if repo.wvfs.isfileorlink(p):
1076 conflicting = p
1076 conflicting = p
1077 break
1077 break
1078 if repo.wvfs.lexists(conflicting):
1078 if repo.wvfs.lexists(conflicting):
1079 orig = scmutil.backuppath(ui, repo, conflicting)
1079 orig = scmutil.backuppath(ui, repo, conflicting)
1080 util.rename(repo.wjoin(conflicting), orig)
1080 util.rename(repo.wjoin(conflicting), orig)
1081 wfctx = wctx[f]
1081 wfctx = wctx[f]
1082 wfctx.clearunknown()
1082 wfctx.clearunknown()
1083 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1083 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1084 size = wfctx.write(
1084 size = wfctx.write(
1085 fctx(f).data(),
1085 fctx(f).data(),
1086 flags,
1086 flags,
1087 backgroundclose=True,
1087 backgroundclose=True,
1088 atomictemp=atomictemp,
1088 atomictemp=atomictemp,
1089 )
1089 )
1090 if wantfiledata:
1090 if wantfiledata:
1091 s = wfctx.lstat()
1091 s = wfctx.lstat()
1092 mode = s.st_mode
1092 mode = s.st_mode
1093 mtime = s[stat.ST_MTIME]
1093 mtime = s[stat.ST_MTIME]
1094 filedata[f] = (mode, size, mtime) # for dirstate.normal
1094 filedata[f] = (mode, size, mtime) # for dirstate.normal
1095 if i == 100:
1095 if i == 100:
1096 yield False, (i, f)
1096 yield False, (i, f)
1097 i = 0
1097 i = 0
1098 i += 1
1098 i += 1
1099 if i > 0:
1099 if i > 0:
1100 yield False, (i, f)
1100 yield False, (i, f)
1101 yield True, filedata
1101 yield True, filedata
1102
1102
1103
1103
1104 def _prefetchfiles(repo, ctx, actions):
1104 def _prefetchfiles(repo, ctx, actions):
1105 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1105 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1106 of merge actions. ``ctx`` is the context being merged in."""
1106 of merge actions. ``ctx`` is the context being merged in."""
1107
1107
1108 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1108 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1109 # don't touch the context to be merged in. 'cd' is skipped, because
1109 # don't touch the context to be merged in. 'cd' is skipped, because
1110 # changed/deleted never resolves to something from the remote side.
1110 # changed/deleted never resolves to something from the remote side.
1111 oplist = [
1111 oplist = [
1112 actions[a]
1112 actions[a]
1113 for a in (
1113 for a in (
1114 mergestatemod.ACTION_GET,
1114 mergestatemod.ACTION_GET,
1115 mergestatemod.ACTION_DELETED_CHANGED,
1115 mergestatemod.ACTION_DELETED_CHANGED,
1116 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1116 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1117 mergestatemod.ACTION_MERGE,
1117 mergestatemod.ACTION_MERGE,
1118 )
1118 )
1119 ]
1119 ]
1120 prefetch = scmutil.prefetchfiles
1120 prefetch = scmutil.prefetchfiles
1121 matchfiles = scmutil.matchfiles
1121 matchfiles = scmutil.matchfiles
1122 prefetch(
1122 prefetch(
1123 repo,
1123 repo,
1124 [ctx.rev()],
1124 [ctx.rev()],
1125 matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
1125 matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
1126 )
1126 )
1127
1127
1128
1128
1129 @attr.s(frozen=True)
1129 @attr.s(frozen=True)
1130 class updateresult(object):
1130 class updateresult(object):
1131 updatedcount = attr.ib()
1131 updatedcount = attr.ib()
1132 mergedcount = attr.ib()
1132 mergedcount = attr.ib()
1133 removedcount = attr.ib()
1133 removedcount = attr.ib()
1134 unresolvedcount = attr.ib()
1134 unresolvedcount = attr.ib()
1135
1135
1136 def isempty(self):
1136 def isempty(self):
1137 return not (
1137 return not (
1138 self.updatedcount
1138 self.updatedcount
1139 or self.mergedcount
1139 or self.mergedcount
1140 or self.removedcount
1140 or self.removedcount
1141 or self.unresolvedcount
1141 or self.unresolvedcount
1142 )
1142 )
1143
1143
1144
1144
1145 def emptyactions():
1145 def emptyactions():
1146 """create an actions dict, to be populated and passed to applyupdates()"""
1146 """create an actions dict, to be populated and passed to applyupdates()"""
1147 return {
1147 return {
1148 m: []
1148 m: []
1149 for m in (
1149 for m in (
1150 mergestatemod.ACTION_ADD,
1150 mergestatemod.ACTION_ADD,
1151 mergestatemod.ACTION_ADD_MODIFIED,
1151 mergestatemod.ACTION_ADD_MODIFIED,
1152 mergestatemod.ACTION_FORGET,
1152 mergestatemod.ACTION_FORGET,
1153 mergestatemod.ACTION_GET,
1153 mergestatemod.ACTION_GET,
1154 mergestatemod.ACTION_CHANGED_DELETED,
1154 mergestatemod.ACTION_CHANGED_DELETED,
1155 mergestatemod.ACTION_DELETED_CHANGED,
1155 mergestatemod.ACTION_DELETED_CHANGED,
1156 mergestatemod.ACTION_REMOVE,
1156 mergestatemod.ACTION_REMOVE,
1157 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1157 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1158 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1158 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1159 mergestatemod.ACTION_MERGE,
1159 mergestatemod.ACTION_MERGE,
1160 mergestatemod.ACTION_EXEC,
1160 mergestatemod.ACTION_EXEC,
1161 mergestatemod.ACTION_KEEP,
1161 mergestatemod.ACTION_KEEP,
1162 mergestatemod.ACTION_PATH_CONFLICT,
1162 mergestatemod.ACTION_PATH_CONFLICT,
1163 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1163 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1164 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1164 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1165 )
1165 )
1166 }
1166 }
1167
1167
1168
1168
1169 def applyupdates(
1169 def applyupdates(
1170 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1170 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1171 ):
1171 ):
1172 """apply the merge action list to the working directory
1172 """apply the merge action list to the working directory
1173
1173
1174 wctx is the working copy context
1174 wctx is the working copy context
1175 mctx is the context to be merged into the working copy
1175 mctx is the context to be merged into the working copy
1176
1176
1177 Return a tuple of (counts, filedata), where counts is a tuple
1177 Return a tuple of (counts, filedata), where counts is a tuple
1178 (updated, merged, removed, unresolved) that describes how many
1178 (updated, merged, removed, unresolved) that describes how many
1179 files were affected by the update, and filedata is as described in
1179 files were affected by the update, and filedata is as described in
1180 batchget.
1180 batchget.
1181 """
1181 """
1182
1182
1183 _prefetchfiles(repo, mctx, actions)
1183 _prefetchfiles(repo, mctx, actions)
1184
1184
1185 updated, merged, removed = 0, 0, 0
1185 updated, merged, removed = 0, 0, 0
1186 ms = mergestatemod.mergestate.clean(
1186 ms = mergestatemod.mergestate.clean(
1187 repo, wctx.p1().node(), mctx.node(), labels
1187 repo, wctx.p1().node(), mctx.node(), labels
1188 )
1188 )
1189
1189
1190 # add ACTION_GET_OTHER_AND_STORE to mergestate
1190 # add ACTION_GET_OTHER_AND_STORE to mergestate
1191 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1191 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1192 ms.addmergedother(e[0])
1192 ms.addmergedother(e[0])
1193
1193
1194 moves = []
1194 moves = []
1195 for m, l in actions.items():
1195 for m, l in actions.items():
1196 l.sort()
1196 l.sort()
1197
1197
1198 # 'cd' and 'dc' actions are treated like other merge conflicts
1198 # 'cd' and 'dc' actions are treated like other merge conflicts
1199 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1199 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1200 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1200 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1201 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1201 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1202 for f, args, msg in mergeactions:
1202 for f, args, msg in mergeactions:
1203 f1, f2, fa, move, anc = args
1203 f1, f2, fa, move, anc = args
1204 if f == b'.hgsubstate': # merged internally
1204 if f == b'.hgsubstate': # merged internally
1205 continue
1205 continue
1206 if f1 is None:
1206 if f1 is None:
1207 fcl = filemerge.absentfilectx(wctx, fa)
1207 fcl = filemerge.absentfilectx(wctx, fa)
1208 else:
1208 else:
1209 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1209 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1210 fcl = wctx[f1]
1210 fcl = wctx[f1]
1211 if f2 is None:
1211 if f2 is None:
1212 fco = filemerge.absentfilectx(mctx, fa)
1212 fco = filemerge.absentfilectx(mctx, fa)
1213 else:
1213 else:
1214 fco = mctx[f2]
1214 fco = mctx[f2]
1215 actx = repo[anc]
1215 actx = repo[anc]
1216 if fa in actx:
1216 if fa in actx:
1217 fca = actx[fa]
1217 fca = actx[fa]
1218 else:
1218 else:
1219 # TODO: move to absentfilectx
1219 # TODO: move to absentfilectx
1220 fca = repo.filectx(f1, fileid=nullrev)
1220 fca = repo.filectx(f1, fileid=nullrev)
1221 ms.add(fcl, fco, fca, f)
1221 ms.add(fcl, fco, fca, f)
1222 if f1 != f and move:
1222 if f1 != f and move:
1223 moves.append(f1)
1223 moves.append(f1)
1224
1224
1225 # remove renamed files after safely stored
1225 # remove renamed files after safely stored
1226 for f in moves:
1226 for f in moves:
1227 if wctx[f].lexists():
1227 if wctx[f].lexists():
1228 repo.ui.debug(b"removing %s\n" % f)
1228 repo.ui.debug(b"removing %s\n" % f)
1229 wctx[f].audit()
1229 wctx[f].audit()
1230 wctx[f].remove()
1230 wctx[f].remove()
1231
1231
1232 numupdates = sum(
1232 numupdates = sum(
1233 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1233 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1234 )
1234 )
1235 progress = repo.ui.makeprogress(
1235 progress = repo.ui.makeprogress(
1236 _(b'updating'), unit=_(b'files'), total=numupdates
1236 _(b'updating'), unit=_(b'files'), total=numupdates
1237 )
1237 )
1238
1238
1239 if [
1239 if [
1240 a
1240 a
1241 for a in actions[mergestatemod.ACTION_REMOVE]
1241 for a in actions[mergestatemod.ACTION_REMOVE]
1242 if a[0] == b'.hgsubstate'
1242 if a[0] == b'.hgsubstate'
1243 ]:
1243 ]:
1244 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1244 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1245
1245
1246 # record path conflicts
1246 # record path conflicts
1247 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1247 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1248 f1, fo = args
1248 f1, fo = args
1249 s = repo.ui.status
1249 s = repo.ui.status
1250 s(
1250 s(
1251 _(
1251 _(
1252 b"%s: path conflict - a file or link has the same name as a "
1252 b"%s: path conflict - a file or link has the same name as a "
1253 b"directory\n"
1253 b"directory\n"
1254 )
1254 )
1255 % f
1255 % f
1256 )
1256 )
1257 if fo == b'l':
1257 if fo == b'l':
1258 s(_(b"the local file has been renamed to %s\n") % f1)
1258 s(_(b"the local file has been renamed to %s\n") % f1)
1259 else:
1259 else:
1260 s(_(b"the remote file has been renamed to %s\n") % f1)
1260 s(_(b"the remote file has been renamed to %s\n") % f1)
1261 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1261 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1262 ms.addpath(f, f1, fo)
1262 ms.addpath(f, f1, fo)
1263 progress.increment(item=f)
1263 progress.increment(item=f)
1264
1264
1265 # When merging in-memory, we can't support worker processes, so set the
1265 # When merging in-memory, we can't support worker processes, so set the
1266 # per-item cost at 0 in that case.
1266 # per-item cost at 0 in that case.
1267 cost = 0 if wctx.isinmemory() else 0.001
1267 cost = 0 if wctx.isinmemory() else 0.001
1268
1268
1269 # remove in parallel (must come before resolving path conflicts and getting)
1269 # remove in parallel (must come before resolving path conflicts and getting)
1270 prog = worker.worker(
1270 prog = worker.worker(
1271 repo.ui,
1271 repo.ui,
1272 cost,
1272 cost,
1273 batchremove,
1273 batchremove,
1274 (repo, wctx),
1274 (repo, wctx),
1275 actions[mergestatemod.ACTION_REMOVE],
1275 actions[mergestatemod.ACTION_REMOVE],
1276 )
1276 )
1277 for i, item in prog:
1277 for i, item in prog:
1278 progress.increment(step=i, item=item)
1278 progress.increment(step=i, item=item)
1279 removed = len(actions[mergestatemod.ACTION_REMOVE])
1279 removed = len(actions[mergestatemod.ACTION_REMOVE])
1280
1280
1281 # resolve path conflicts (must come before getting)
1281 # resolve path conflicts (must come before getting)
1282 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1282 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1283 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1283 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1284 (f0, origf0) = args
1284 (f0, origf0) = args
1285 if wctx[f0].lexists():
1285 if wctx[f0].lexists():
1286 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1286 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1287 wctx[f].audit()
1287 wctx[f].audit()
1288 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1288 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1289 wctx[f0].remove()
1289 wctx[f0].remove()
1290 progress.increment(item=f)
1290 progress.increment(item=f)
1291
1291
1292 # get in parallel.
1292 # get in parallel.
1293 threadsafe = repo.ui.configbool(
1293 threadsafe = repo.ui.configbool(
1294 b'experimental', b'worker.wdir-get-thread-safe'
1294 b'experimental', b'worker.wdir-get-thread-safe'
1295 )
1295 )
1296 prog = worker.worker(
1296 prog = worker.worker(
1297 repo.ui,
1297 repo.ui,
1298 cost,
1298 cost,
1299 batchget,
1299 batchget,
1300 (repo, mctx, wctx, wantfiledata),
1300 (repo, mctx, wctx, wantfiledata),
1301 actions[mergestatemod.ACTION_GET],
1301 actions[mergestatemod.ACTION_GET],
1302 threadsafe=threadsafe,
1302 threadsafe=threadsafe,
1303 hasretval=True,
1303 hasretval=True,
1304 )
1304 )
1305 getfiledata = {}
1305 getfiledata = {}
1306 for final, res in prog:
1306 for final, res in prog:
1307 if final:
1307 if final:
1308 getfiledata = res
1308 getfiledata = res
1309 else:
1309 else:
1310 i, item = res
1310 i, item = res
1311 progress.increment(step=i, item=item)
1311 progress.increment(step=i, item=item)
1312 updated = len(actions[mergestatemod.ACTION_GET])
1312 updated = len(actions[mergestatemod.ACTION_GET])
1313
1313
1314 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1314 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1315 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1315 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1316
1316
1317 # forget (manifest only, just log it) (must come first)
1317 # forget (manifest only, just log it) (must come first)
1318 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1318 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1319 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1319 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1320 progress.increment(item=f)
1320 progress.increment(item=f)
1321
1321
1322 # re-add (manifest only, just log it)
1322 # re-add (manifest only, just log it)
1323 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1323 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1324 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1324 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1325 progress.increment(item=f)
1325 progress.increment(item=f)
1326
1326
1327 # re-add/mark as modified (manifest only, just log it)
1327 # re-add/mark as modified (manifest only, just log it)
1328 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1328 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1329 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1329 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1330 progress.increment(item=f)
1330 progress.increment(item=f)
1331
1331
1332 # keep (noop, just log it)
1332 # keep (noop, just log it)
1333 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
1333 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
1334 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1334 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1335 # no progress
1335 # no progress
1336
1336
1337 # directory rename, move local
1337 # directory rename, move local
1338 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1338 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1339 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1339 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1340 progress.increment(item=f)
1340 progress.increment(item=f)
1341 f0, flags = args
1341 f0, flags = args
1342 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1342 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1343 wctx[f].audit()
1343 wctx[f].audit()
1344 wctx[f].write(wctx.filectx(f0).data(), flags)
1344 wctx[f].write(wctx.filectx(f0).data(), flags)
1345 wctx[f0].remove()
1345 wctx[f0].remove()
1346 updated += 1
1346 updated += 1
1347
1347
1348 # local directory rename, get
1348 # local directory rename, get
1349 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1349 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1350 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1350 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1351 progress.increment(item=f)
1351 progress.increment(item=f)
1352 f0, flags = args
1352 f0, flags = args
1353 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1353 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1354 wctx[f].write(mctx.filectx(f0).data(), flags)
1354 wctx[f].write(mctx.filectx(f0).data(), flags)
1355 updated += 1
1355 updated += 1
1356
1356
1357 # exec
1357 # exec
1358 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
1358 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
1359 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1359 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1360 progress.increment(item=f)
1360 progress.increment(item=f)
1361 (flags,) = args
1361 (flags,) = args
1362 wctx[f].audit()
1362 wctx[f].audit()
1363 wctx[f].setflags(b'l' in flags, b'x' in flags)
1363 wctx[f].setflags(b'l' in flags, b'x' in flags)
1364 updated += 1
1364 updated += 1
1365
1365
1366 # the ordering is important here -- ms.mergedriver will raise if the merge
1366 # the ordering is important here -- ms.mergedriver will raise if the merge
1367 # driver has changed, and we want to be able to bypass it when overwrite is
1367 # driver has changed, and we want to be able to bypass it when overwrite is
1368 # True
1368 # True
1369 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1369 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1370
1370
1371 if usemergedriver:
1371 if usemergedriver:
1372 if wctx.isinmemory():
1372 if wctx.isinmemory():
1373 raise error.InMemoryMergeConflictsError(
1373 raise error.InMemoryMergeConflictsError(
1374 b"in-memory merge does not support mergedriver"
1374 b"in-memory merge does not support mergedriver"
1375 )
1375 )
1376 ms.commit()
1376 ms.commit()
1377 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1377 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1378 # the driver might leave some files unresolved
1378 # the driver might leave some files unresolved
1379 unresolvedf = set(ms.unresolved())
1379 unresolvedf = set(ms.unresolved())
1380 if not proceed:
1380 if not proceed:
1381 # XXX setting unresolved to at least 1 is a hack to make sure we
1381 # XXX setting unresolved to at least 1 is a hack to make sure we
1382 # error out
1382 # error out
1383 return updateresult(
1383 return updateresult(
1384 updated, merged, removed, max(len(unresolvedf), 1)
1384 updated, merged, removed, max(len(unresolvedf), 1)
1385 )
1385 )
1386 newactions = []
1386 newactions = []
1387 for f, args, msg in mergeactions:
1387 for f, args, msg in mergeactions:
1388 if f in unresolvedf:
1388 if f in unresolvedf:
1389 newactions.append((f, args, msg))
1389 newactions.append((f, args, msg))
1390 mergeactions = newactions
1390 mergeactions = newactions
1391
1391
1392 try:
1392 try:
1393 # premerge
1393 # premerge
1394 tocomplete = []
1394 tocomplete = []
1395 for f, args, msg in mergeactions:
1395 for f, args, msg in mergeactions:
1396 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1396 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1397 progress.increment(item=f)
1397 progress.increment(item=f)
1398 if f == b'.hgsubstate': # subrepo states need updating
1398 if f == b'.hgsubstate': # subrepo states need updating
1399 subrepoutil.submerge(
1399 subrepoutil.submerge(
1400 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1400 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1401 )
1401 )
1402 continue
1402 continue
1403 wctx[f].audit()
1403 wctx[f].audit()
1404 complete, r = ms.preresolve(f, wctx)
1404 complete, r = ms.preresolve(f, wctx)
1405 if not complete:
1405 if not complete:
1406 numupdates += 1
1406 numupdates += 1
1407 tocomplete.append((f, args, msg))
1407 tocomplete.append((f, args, msg))
1408
1408
1409 # merge
1409 # merge
1410 for f, args, msg in tocomplete:
1410 for f, args, msg in tocomplete:
1411 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1411 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1412 progress.increment(item=f, total=numupdates)
1412 progress.increment(item=f, total=numupdates)
1413 ms.resolve(f, wctx)
1413 ms.resolve(f, wctx)
1414
1414
1415 finally:
1415 finally:
1416 ms.commit()
1416 ms.commit()
1417
1417
1418 unresolved = ms.unresolvedcount()
1418 unresolved = ms.unresolvedcount()
1419
1419
1420 if (
1420 if (
1421 usemergedriver
1421 usemergedriver
1422 and not unresolved
1422 and not unresolved
1423 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1423 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1424 ):
1424 ):
1425 if not driverconclude(repo, ms, wctx, labels=labels):
1425 if not driverconclude(repo, ms, wctx, labels=labels):
1426 # XXX setting unresolved to at least 1 is a hack to make sure we
1426 # XXX setting unresolved to at least 1 is a hack to make sure we
1427 # error out
1427 # error out
1428 unresolved = max(unresolved, 1)
1428 unresolved = max(unresolved, 1)
1429
1429
1430 ms.commit()
1430 ms.commit()
1431
1431
1432 msupdated, msmerged, msremoved = ms.counts()
1432 msupdated, msmerged, msremoved = ms.counts()
1433 updated += msupdated
1433 updated += msupdated
1434 merged += msmerged
1434 merged += msmerged
1435 removed += msremoved
1435 removed += msremoved
1436
1436
1437 extraactions = ms.actions()
1437 extraactions = ms.actions()
1438 if extraactions:
1438 if extraactions:
1439 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
1439 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
1440 for k, acts in pycompat.iteritems(extraactions):
1440 for k, acts in pycompat.iteritems(extraactions):
1441 actions[k].extend(acts)
1441 actions[k].extend(acts)
1442 if k == mergestatemod.ACTION_GET and wantfiledata:
1442 if k == mergestatemod.ACTION_GET and wantfiledata:
1443 # no filedata until mergestate is updated to provide it
1443 # no filedata until mergestate is updated to provide it
1444 for a in acts:
1444 for a in acts:
1445 getfiledata[a[0]] = None
1445 getfiledata[a[0]] = None
1446 # Remove these files from actions[ACTION_MERGE] as well. This is
1446 # Remove these files from actions[ACTION_MERGE] as well. This is
1447 # important because in recordupdates, files in actions[ACTION_MERGE]
1447 # important because in recordupdates, files in actions[ACTION_MERGE]
1448 # are processed after files in other actions, and the merge driver
1448 # are processed after files in other actions, and the merge driver
1449 # might add files to those actions via extraactions above. This can
1449 # might add files to those actions via extraactions above. This can
1450 # lead to a file being recorded twice, with poor results. This is
1450 # lead to a file being recorded twice, with poor results. This is
1451 # especially problematic for actions[ACTION_REMOVE] (currently only
1451 # especially problematic for actions[ACTION_REMOVE] (currently only
1452 # possible with the merge driver in the initial merge process;
1452 # possible with the merge driver in the initial merge process;
1453 # interrupted merges don't go through this flow).
1453 # interrupted merges don't go through this flow).
1454 #
1454 #
1455 # The real fix here is to have indexes by both file and action so
1455 # The real fix here is to have indexes by both file and action so
1456 # that when the action for a file is changed it is automatically
1456 # that when the action for a file is changed it is automatically
1457 # reflected in the other action lists. But that involves a more
1457 # reflected in the other action lists. But that involves a more
1458 # complex data structure, so this will do for now.
1458 # complex data structure, so this will do for now.
1459 #
1459 #
1460 # We don't need to do the same operation for 'dc' and 'cd' because
1460 # We don't need to do the same operation for 'dc' and 'cd' because
1461 # those lists aren't consulted again.
1461 # those lists aren't consulted again.
1462 mfiles.difference_update(a[0] for a in acts)
1462 mfiles.difference_update(a[0] for a in acts)
1463
1463
1464 actions[mergestatemod.ACTION_MERGE] = [
1464 actions[mergestatemod.ACTION_MERGE] = [
1465 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
1465 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
1466 ]
1466 ]
1467
1467
1468 progress.complete()
1468 progress.complete()
1469 assert len(getfiledata) == (
1469 assert len(getfiledata) == (
1470 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1470 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1471 )
1471 )
1472 return updateresult(updated, merged, removed, unresolved), getfiledata
1472 return updateresult(updated, merged, removed, unresolved), getfiledata
1473
1473
1474
1474
1475 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1475 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1476 UPDATECHECK_NONE = b'none'
1476 UPDATECHECK_NONE = b'none'
1477 UPDATECHECK_LINEAR = b'linear'
1477 UPDATECHECK_LINEAR = b'linear'
1478 UPDATECHECK_NO_CONFLICT = b'noconflict'
1478 UPDATECHECK_NO_CONFLICT = b'noconflict'
1479
1479
1480
1480
1481 def update(
1481 def update(
1482 repo,
1482 repo,
1483 node,
1483 node,
1484 branchmerge,
1484 branchmerge,
1485 force,
1485 force,
1486 ancestor=None,
1486 ancestor=None,
1487 mergeancestor=False,
1487 mergeancestor=False,
1488 labels=None,
1488 labels=None,
1489 matcher=None,
1489 matcher=None,
1490 mergeforce=False,
1490 mergeforce=False,
1491 updatedirstate=True,
1491 updatedirstate=True,
1492 updatecheck=None,
1492 updatecheck=None,
1493 wc=None,
1493 wc=None,
1494 ):
1494 ):
1495 """
1495 """
1496 Perform a merge between the working directory and the given node
1496 Perform a merge between the working directory and the given node
1497
1497
1498 node = the node to update to
1498 node = the node to update to
1499 branchmerge = whether to merge between branches
1499 branchmerge = whether to merge between branches
1500 force = whether to force branch merging or file overwriting
1500 force = whether to force branch merging or file overwriting
1501 matcher = a matcher to filter file lists (dirstate not updated)
1501 matcher = a matcher to filter file lists (dirstate not updated)
1502 mergeancestor = whether it is merging with an ancestor. If true,
1502 mergeancestor = whether it is merging with an ancestor. If true,
1503 we should accept the incoming changes for any prompts that occur.
1503 we should accept the incoming changes for any prompts that occur.
1504 If false, merging with an ancestor (fast-forward) is only allowed
1504 If false, merging with an ancestor (fast-forward) is only allowed
1505 between different named branches. This flag is used by rebase extension
1505 between different named branches. This flag is used by rebase extension
1506 as a temporary fix and should be avoided in general.
1506 as a temporary fix and should be avoided in general.
1507 labels = labels to use for base, local and other
1507 labels = labels to use for base, local and other
1508 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1508 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1509 this is True, then 'force' should be True as well.
1509 this is True, then 'force' should be True as well.
1510
1510
1511 The table below shows all the behaviors of the update command given the
1511 The table below shows all the behaviors of the update command given the
1512 -c/--check and -C/--clean or no options, whether the working directory is
1512 -c/--check and -C/--clean or no options, whether the working directory is
1513 dirty, whether a revision is specified, and the relationship of the parent
1513 dirty, whether a revision is specified, and the relationship of the parent
1514 rev to the target rev (linear or not). Match from top first. The -n
1514 rev to the target rev (linear or not). Match from top first. The -n
1515 option doesn't exist on the command line, but represents the
1515 option doesn't exist on the command line, but represents the
1516 experimental.updatecheck=noconflict option.
1516 experimental.updatecheck=noconflict option.
1517
1517
1518 This logic is tested by test-update-branches.t.
1518 This logic is tested by test-update-branches.t.
1519
1519
1520 -c -C -n -m dirty rev linear | result
1520 -c -C -n -m dirty rev linear | result
1521 y y * * * * * | (1)
1521 y y * * * * * | (1)
1522 y * y * * * * | (1)
1522 y * y * * * * | (1)
1523 y * * y * * * | (1)
1523 y * * y * * * | (1)
1524 * y y * * * * | (1)
1524 * y y * * * * | (1)
1525 * y * y * * * | (1)
1525 * y * y * * * | (1)
1526 * * y y * * * | (1)
1526 * * y y * * * | (1)
1527 * * * * * n n | x
1527 * * * * * n n | x
1528 * * * * n * * | ok
1528 * * * * n * * | ok
1529 n n n n y * y | merge
1529 n n n n y * y | merge
1530 n n n n y y n | (2)
1530 n n n n y y n | (2)
1531 n n n y y * * | merge
1531 n n n y y * * | merge
1532 n n y n y * * | merge if no conflict
1532 n n y n y * * | merge if no conflict
1533 n y n n y * * | discard
1533 n y n n y * * | discard
1534 y n n n y * * | (3)
1534 y n n n y * * | (3)
1535
1535
1536 x = can't happen
1536 x = can't happen
1537 * = don't-care
1537 * = don't-care
1538 1 = incompatible options (checked in commands.py)
1538 1 = incompatible options (checked in commands.py)
1539 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1539 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1540 3 = abort: uncommitted changes (checked in commands.py)
1540 3 = abort: uncommitted changes (checked in commands.py)
1541
1541
1542 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1542 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1543 to repo[None] if None is passed.
1543 to repo[None] if None is passed.
1544
1544
1545 Return the same tuple as applyupdates().
1545 Return the same tuple as applyupdates().
1546 """
1546 """
1547 # Avoid cycle.
1547 # Avoid cycle.
1548 from . import sparse
1548 from . import sparse
1549
1549
1550 # This function used to find the default destination if node was None, but
1550 # This function used to find the default destination if node was None, but
1551 # that's now in destutil.py.
1551 # that's now in destutil.py.
1552 assert node is not None
1552 assert node is not None
1553 if not branchmerge and not force:
1553 if not branchmerge and not force:
1554 # TODO: remove the default once all callers that pass branchmerge=False
1554 # TODO: remove the default once all callers that pass branchmerge=False
1555 # and force=False pass a value for updatecheck. We may want to allow
1555 # and force=False pass a value for updatecheck. We may want to allow
1556 # updatecheck='abort' to better suppport some of these callers.
1556 # updatecheck='abort' to better suppport some of these callers.
1557 if updatecheck is None:
1557 if updatecheck is None:
1558 updatecheck = UPDATECHECK_LINEAR
1558 updatecheck = UPDATECHECK_LINEAR
1559 if updatecheck not in (
1559 if updatecheck not in (
1560 UPDATECHECK_NONE,
1560 UPDATECHECK_NONE,
1561 UPDATECHECK_LINEAR,
1561 UPDATECHECK_LINEAR,
1562 UPDATECHECK_NO_CONFLICT,
1562 UPDATECHECK_NO_CONFLICT,
1563 ):
1563 ):
1564 raise ValueError(
1564 raise ValueError(
1565 r'Invalid updatecheck %r (can accept %r)'
1565 r'Invalid updatecheck %r (can accept %r)'
1566 % (
1566 % (
1567 updatecheck,
1567 updatecheck,
1568 (
1568 (
1569 UPDATECHECK_NONE,
1569 UPDATECHECK_NONE,
1570 UPDATECHECK_LINEAR,
1570 UPDATECHECK_LINEAR,
1571 UPDATECHECK_NO_CONFLICT,
1571 UPDATECHECK_NO_CONFLICT,
1572 ),
1572 ),
1573 )
1573 )
1574 )
1574 )
1575 with repo.wlock():
1575 if wc is not None and wc.isinmemory():
1576 maybe_wlock = util.nullcontextmanager()
1577 else:
1578 maybe_wlock = repo.wlock()
1579 with maybe_wlock:
1576 if wc is None:
1580 if wc is None:
1577 wc = repo[None]
1581 wc = repo[None]
1578 pl = wc.parents()
1582 pl = wc.parents()
1579 p1 = pl[0]
1583 p1 = pl[0]
1580 p2 = repo[node]
1584 p2 = repo[node]
1581 if ancestor is not None:
1585 if ancestor is not None:
1582 pas = [repo[ancestor]]
1586 pas = [repo[ancestor]]
1583 else:
1587 else:
1584 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1588 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1585 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1589 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1586 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1590 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1587 else:
1591 else:
1588 pas = [p1.ancestor(p2, warn=branchmerge)]
1592 pas = [p1.ancestor(p2, warn=branchmerge)]
1589
1593
1590 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1594 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1591
1595
1592 overwrite = force and not branchmerge
1596 overwrite = force and not branchmerge
1593 ### check phase
1597 ### check phase
1594 if not overwrite:
1598 if not overwrite:
1595 if len(pl) > 1:
1599 if len(pl) > 1:
1596 raise error.Abort(_(b"outstanding uncommitted merge"))
1600 raise error.Abort(_(b"outstanding uncommitted merge"))
1597 ms = mergestatemod.mergestate.read(repo)
1601 ms = mergestatemod.mergestate.read(repo)
1598 if list(ms.unresolved()):
1602 if list(ms.unresolved()):
1599 raise error.Abort(
1603 raise error.Abort(
1600 _(b"outstanding merge conflicts"),
1604 _(b"outstanding merge conflicts"),
1601 hint=_(b"use 'hg resolve' to resolve"),
1605 hint=_(b"use 'hg resolve' to resolve"),
1602 )
1606 )
1603 if branchmerge:
1607 if branchmerge:
1604 if pas == [p2]:
1608 if pas == [p2]:
1605 raise error.Abort(
1609 raise error.Abort(
1606 _(
1610 _(
1607 b"merging with a working directory ancestor"
1611 b"merging with a working directory ancestor"
1608 b" has no effect"
1612 b" has no effect"
1609 )
1613 )
1610 )
1614 )
1611 elif pas == [p1]:
1615 elif pas == [p1]:
1612 if not mergeancestor and wc.branch() == p2.branch():
1616 if not mergeancestor and wc.branch() == p2.branch():
1613 raise error.Abort(
1617 raise error.Abort(
1614 _(b"nothing to merge"),
1618 _(b"nothing to merge"),
1615 hint=_(b"use 'hg update' or check 'hg heads'"),
1619 hint=_(b"use 'hg update' or check 'hg heads'"),
1616 )
1620 )
1617 if not force and (wc.files() or wc.deleted()):
1621 if not force and (wc.files() or wc.deleted()):
1618 raise error.Abort(
1622 raise error.Abort(
1619 _(b"uncommitted changes"),
1623 _(b"uncommitted changes"),
1620 hint=_(b"use 'hg status' to list changes"),
1624 hint=_(b"use 'hg status' to list changes"),
1621 )
1625 )
1622 if not wc.isinmemory():
1626 if not wc.isinmemory():
1623 for s in sorted(wc.substate):
1627 for s in sorted(wc.substate):
1624 wc.sub(s).bailifchanged()
1628 wc.sub(s).bailifchanged()
1625
1629
1626 elif not overwrite:
1630 elif not overwrite:
1627 if p1 == p2: # no-op update
1631 if p1 == p2: # no-op update
1628 # call the hooks and exit early
1632 # call the hooks and exit early
1629 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1633 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1630 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1634 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1631 return updateresult(0, 0, 0, 0)
1635 return updateresult(0, 0, 0, 0)
1632
1636
1633 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1637 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1634 [p1],
1638 [p1],
1635 [p2],
1639 [p2],
1636 ): # nonlinear
1640 ): # nonlinear
1637 dirty = wc.dirty(missing=True)
1641 dirty = wc.dirty(missing=True)
1638 if dirty:
1642 if dirty:
1639 # Branching is a bit strange to ensure we do the minimal
1643 # Branching is a bit strange to ensure we do the minimal
1640 # amount of call to obsutil.foreground.
1644 # amount of call to obsutil.foreground.
1641 foreground = obsutil.foreground(repo, [p1.node()])
1645 foreground = obsutil.foreground(repo, [p1.node()])
1642 # note: the <node> variable contains a random identifier
1646 # note: the <node> variable contains a random identifier
1643 if repo[node].node() in foreground:
1647 if repo[node].node() in foreground:
1644 pass # allow updating to successors
1648 pass # allow updating to successors
1645 else:
1649 else:
1646 msg = _(b"uncommitted changes")
1650 msg = _(b"uncommitted changes")
1647 hint = _(b"commit or update --clean to discard changes")
1651 hint = _(b"commit or update --clean to discard changes")
1648 raise error.UpdateAbort(msg, hint=hint)
1652 raise error.UpdateAbort(msg, hint=hint)
1649 else:
1653 else:
1650 # Allow jumping branches if clean and specific rev given
1654 # Allow jumping branches if clean and specific rev given
1651 pass
1655 pass
1652
1656
1653 if overwrite:
1657 if overwrite:
1654 pas = [wc]
1658 pas = [wc]
1655 elif not branchmerge:
1659 elif not branchmerge:
1656 pas = [p1]
1660 pas = [p1]
1657
1661
1658 # deprecated config: merge.followcopies
1662 # deprecated config: merge.followcopies
1659 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1663 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1660 if overwrite:
1664 if overwrite:
1661 followcopies = False
1665 followcopies = False
1662 elif not pas[0]:
1666 elif not pas[0]:
1663 followcopies = False
1667 followcopies = False
1664 if not branchmerge and not wc.dirty(missing=True):
1668 if not branchmerge and not wc.dirty(missing=True):
1665 followcopies = False
1669 followcopies = False
1666
1670
1667 ### calculate phase
1671 ### calculate phase
1668 actionbyfile, diverge, renamedelete = calculateupdates(
1672 actionbyfile, diverge, renamedelete = calculateupdates(
1669 repo,
1673 repo,
1670 wc,
1674 wc,
1671 p2,
1675 p2,
1672 pas,
1676 pas,
1673 branchmerge,
1677 branchmerge,
1674 force,
1678 force,
1675 mergeancestor,
1679 mergeancestor,
1676 followcopies,
1680 followcopies,
1677 matcher=matcher,
1681 matcher=matcher,
1678 mergeforce=mergeforce,
1682 mergeforce=mergeforce,
1679 )
1683 )
1680
1684
1681 if updatecheck == UPDATECHECK_NO_CONFLICT:
1685 if updatecheck == UPDATECHECK_NO_CONFLICT:
1682 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1686 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1683 if m not in (
1687 if m not in (
1684 mergestatemod.ACTION_GET,
1688 mergestatemod.ACTION_GET,
1685 mergestatemod.ACTION_KEEP,
1689 mergestatemod.ACTION_KEEP,
1686 mergestatemod.ACTION_EXEC,
1690 mergestatemod.ACTION_EXEC,
1687 mergestatemod.ACTION_REMOVE,
1691 mergestatemod.ACTION_REMOVE,
1688 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1692 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1689 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1693 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1690 ):
1694 ):
1691 msg = _(b"conflicting changes")
1695 msg = _(b"conflicting changes")
1692 hint = _(b"commit or update --clean to discard changes")
1696 hint = _(b"commit or update --clean to discard changes")
1693 raise error.Abort(msg, hint=hint)
1697 raise error.Abort(msg, hint=hint)
1694
1698
1695 # Prompt and create actions. Most of this is in the resolve phase
1699 # Prompt and create actions. Most of this is in the resolve phase
1696 # already, but we can't handle .hgsubstate in filemerge or
1700 # already, but we can't handle .hgsubstate in filemerge or
1697 # subrepoutil.submerge yet so we have to keep prompting for it.
1701 # subrepoutil.submerge yet so we have to keep prompting for it.
1698 if b'.hgsubstate' in actionbyfile:
1702 if b'.hgsubstate' in actionbyfile:
1699 f = b'.hgsubstate'
1703 f = b'.hgsubstate'
1700 m, args, msg = actionbyfile[f]
1704 m, args, msg = actionbyfile[f]
1701 prompts = filemerge.partextras(labels)
1705 prompts = filemerge.partextras(labels)
1702 prompts[b'f'] = f
1706 prompts[b'f'] = f
1703 if m == mergestatemod.ACTION_CHANGED_DELETED:
1707 if m == mergestatemod.ACTION_CHANGED_DELETED:
1704 if repo.ui.promptchoice(
1708 if repo.ui.promptchoice(
1705 _(
1709 _(
1706 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1710 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1707 b"use (c)hanged version or (d)elete?"
1711 b"use (c)hanged version or (d)elete?"
1708 b"$$ &Changed $$ &Delete"
1712 b"$$ &Changed $$ &Delete"
1709 )
1713 )
1710 % prompts,
1714 % prompts,
1711 0,
1715 0,
1712 ):
1716 ):
1713 actionbyfile[f] = (
1717 actionbyfile[f] = (
1714 mergestatemod.ACTION_REMOVE,
1718 mergestatemod.ACTION_REMOVE,
1715 None,
1719 None,
1716 b'prompt delete',
1720 b'prompt delete',
1717 )
1721 )
1718 elif f in p1:
1722 elif f in p1:
1719 actionbyfile[f] = (
1723 actionbyfile[f] = (
1720 mergestatemod.ACTION_ADD_MODIFIED,
1724 mergestatemod.ACTION_ADD_MODIFIED,
1721 None,
1725 None,
1722 b'prompt keep',
1726 b'prompt keep',
1723 )
1727 )
1724 else:
1728 else:
1725 actionbyfile[f] = (
1729 actionbyfile[f] = (
1726 mergestatemod.ACTION_ADD,
1730 mergestatemod.ACTION_ADD,
1727 None,
1731 None,
1728 b'prompt keep',
1732 b'prompt keep',
1729 )
1733 )
1730 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1734 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1731 f1, f2, fa, move, anc = args
1735 f1, f2, fa, move, anc = args
1732 flags = p2[f2].flags()
1736 flags = p2[f2].flags()
1733 if (
1737 if (
1734 repo.ui.promptchoice(
1738 repo.ui.promptchoice(
1735 _(
1739 _(
1736 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1740 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1737 b"use (c)hanged version or leave (d)eleted?"
1741 b"use (c)hanged version or leave (d)eleted?"
1738 b"$$ &Changed $$ &Deleted"
1742 b"$$ &Changed $$ &Deleted"
1739 )
1743 )
1740 % prompts,
1744 % prompts,
1741 0,
1745 0,
1742 )
1746 )
1743 == 0
1747 == 0
1744 ):
1748 ):
1745 actionbyfile[f] = (
1749 actionbyfile[f] = (
1746 mergestatemod.ACTION_GET,
1750 mergestatemod.ACTION_GET,
1747 (flags, False),
1751 (flags, False),
1748 b'prompt recreating',
1752 b'prompt recreating',
1749 )
1753 )
1750 else:
1754 else:
1751 del actionbyfile[f]
1755 del actionbyfile[f]
1752
1756
1753 # Convert to dictionary-of-lists format
1757 # Convert to dictionary-of-lists format
1754 actions = emptyactions()
1758 actions = emptyactions()
1755 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1759 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1756 if m not in actions:
1760 if m not in actions:
1757 actions[m] = []
1761 actions[m] = []
1758 actions[m].append((f, args, msg))
1762 actions[m].append((f, args, msg))
1759
1763
1760 # ACTION_GET_OTHER_AND_STORE is a mergestatemod.ACTION_GET + store in mergestate
1764 # ACTION_GET_OTHER_AND_STORE is a mergestatemod.ACTION_GET + store in mergestate
1761 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1765 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1762 actions[mergestatemod.ACTION_GET].append(e)
1766 actions[mergestatemod.ACTION_GET].append(e)
1763
1767
1764 if not util.fscasesensitive(repo.path):
1768 if not util.fscasesensitive(repo.path):
1765 # check collision between files only in p2 for clean update
1769 # check collision between files only in p2 for clean update
1766 if not branchmerge and (
1770 if not branchmerge and (
1767 force or not wc.dirty(missing=True, branch=False)
1771 force or not wc.dirty(missing=True, branch=False)
1768 ):
1772 ):
1769 _checkcollision(repo, p2.manifest(), None)
1773 _checkcollision(repo, p2.manifest(), None)
1770 else:
1774 else:
1771 _checkcollision(repo, wc.manifest(), actions)
1775 _checkcollision(repo, wc.manifest(), actions)
1772
1776
1773 # divergent renames
1777 # divergent renames
1774 for f, fl in sorted(pycompat.iteritems(diverge)):
1778 for f, fl in sorted(pycompat.iteritems(diverge)):
1775 repo.ui.warn(
1779 repo.ui.warn(
1776 _(
1780 _(
1777 b"note: possible conflict - %s was renamed "
1781 b"note: possible conflict - %s was renamed "
1778 b"multiple times to:\n"
1782 b"multiple times to:\n"
1779 )
1783 )
1780 % f
1784 % f
1781 )
1785 )
1782 for nf in sorted(fl):
1786 for nf in sorted(fl):
1783 repo.ui.warn(b" %s\n" % nf)
1787 repo.ui.warn(b" %s\n" % nf)
1784
1788
1785 # rename and delete
1789 # rename and delete
1786 for f, fl in sorted(pycompat.iteritems(renamedelete)):
1790 for f, fl in sorted(pycompat.iteritems(renamedelete)):
1787 repo.ui.warn(
1791 repo.ui.warn(
1788 _(
1792 _(
1789 b"note: possible conflict - %s was deleted "
1793 b"note: possible conflict - %s was deleted "
1790 b"and renamed to:\n"
1794 b"and renamed to:\n"
1791 )
1795 )
1792 % f
1796 % f
1793 )
1797 )
1794 for nf in sorted(fl):
1798 for nf in sorted(fl):
1795 repo.ui.warn(b" %s\n" % nf)
1799 repo.ui.warn(b" %s\n" % nf)
1796
1800
1797 ### apply phase
1801 ### apply phase
1798 if not branchmerge: # just jump to the new rev
1802 if not branchmerge: # just jump to the new rev
1799 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1803 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1800 # If we're doing a partial update, we need to skip updating
1804 # If we're doing a partial update, we need to skip updating
1801 # the dirstate.
1805 # the dirstate.
1802 always = matcher is None or matcher.always()
1806 always = matcher is None or matcher.always()
1803 updatedirstate = updatedirstate and always and not wc.isinmemory()
1807 updatedirstate = updatedirstate and always and not wc.isinmemory()
1804 if updatedirstate:
1808 if updatedirstate:
1805 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
1809 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
1806 # note that we're in the middle of an update
1810 # note that we're in the middle of an update
1807 repo.vfs.write(b'updatestate', p2.hex())
1811 repo.vfs.write(b'updatestate', p2.hex())
1808
1812
1809 # Advertise fsmonitor when its presence could be useful.
1813 # Advertise fsmonitor when its presence could be useful.
1810 #
1814 #
1811 # We only advertise when performing an update from an empty working
1815 # We only advertise when performing an update from an empty working
1812 # directory. This typically only occurs during initial clone.
1816 # directory. This typically only occurs during initial clone.
1813 #
1817 #
1814 # We give users a mechanism to disable the warning in case it is
1818 # We give users a mechanism to disable the warning in case it is
1815 # annoying.
1819 # annoying.
1816 #
1820 #
1817 # We only allow on Linux and MacOS because that's where fsmonitor is
1821 # We only allow on Linux and MacOS because that's where fsmonitor is
1818 # considered stable.
1822 # considered stable.
1819 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1823 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1820 fsmonitorthreshold = repo.ui.configint(
1824 fsmonitorthreshold = repo.ui.configint(
1821 b'fsmonitor', b'warn_update_file_count'
1825 b'fsmonitor', b'warn_update_file_count'
1822 )
1826 )
1823 try:
1827 try:
1824 # avoid cycle: extensions -> cmdutil -> merge
1828 # avoid cycle: extensions -> cmdutil -> merge
1825 from . import extensions
1829 from . import extensions
1826
1830
1827 extensions.find(b'fsmonitor')
1831 extensions.find(b'fsmonitor')
1828 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1832 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1829 # We intentionally don't look at whether fsmonitor has disabled
1833 # We intentionally don't look at whether fsmonitor has disabled
1830 # itself because a) fsmonitor may have already printed a warning
1834 # itself because a) fsmonitor may have already printed a warning
1831 # b) we only care about the config state here.
1835 # b) we only care about the config state here.
1832 except KeyError:
1836 except KeyError:
1833 fsmonitorenabled = False
1837 fsmonitorenabled = False
1834
1838
1835 if (
1839 if (
1836 fsmonitorwarning
1840 fsmonitorwarning
1837 and not fsmonitorenabled
1841 and not fsmonitorenabled
1838 and p1.node() == nullid
1842 and p1.node() == nullid
1839 and len(actions[mergestatemod.ACTION_GET]) >= fsmonitorthreshold
1843 and len(actions[mergestatemod.ACTION_GET]) >= fsmonitorthreshold
1840 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1844 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1841 ):
1845 ):
1842 repo.ui.warn(
1846 repo.ui.warn(
1843 _(
1847 _(
1844 b'(warning: large working directory being used without '
1848 b'(warning: large working directory being used without '
1845 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1849 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1846 b'see "hg help -e fsmonitor")\n'
1850 b'see "hg help -e fsmonitor")\n'
1847 )
1851 )
1848 )
1852 )
1849
1853
1850 wantfiledata = updatedirstate and not branchmerge
1854 wantfiledata = updatedirstate and not branchmerge
1851 stats, getfiledata = applyupdates(
1855 stats, getfiledata = applyupdates(
1852 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
1856 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
1853 )
1857 )
1854
1858
1855 if updatedirstate:
1859 if updatedirstate:
1856 with repo.dirstate.parentchange():
1860 with repo.dirstate.parentchange():
1857 repo.setparents(fp1, fp2)
1861 repo.setparents(fp1, fp2)
1858 mergestatemod.recordupdates(
1862 mergestatemod.recordupdates(
1859 repo, actions, branchmerge, getfiledata
1863 repo, actions, branchmerge, getfiledata
1860 )
1864 )
1861 # update completed, clear state
1865 # update completed, clear state
1862 util.unlink(repo.vfs.join(b'updatestate'))
1866 util.unlink(repo.vfs.join(b'updatestate'))
1863
1867
1864 if not branchmerge:
1868 if not branchmerge:
1865 repo.dirstate.setbranch(p2.branch())
1869 repo.dirstate.setbranch(p2.branch())
1866
1870
1867 # If we're updating to a location, clean up any stale temporary includes
1871 # If we're updating to a location, clean up any stale temporary includes
1868 # (ex: this happens during hg rebase --abort).
1872 # (ex: this happens during hg rebase --abort).
1869 if not branchmerge:
1873 if not branchmerge:
1870 sparse.prunetemporaryincludes(repo)
1874 sparse.prunetemporaryincludes(repo)
1871
1875
1872 if updatedirstate:
1876 if updatedirstate:
1873 repo.hook(
1877 repo.hook(
1874 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
1878 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
1875 )
1879 )
1876 return stats
1880 return stats
1877
1881
1878
1882
1879 def merge(ctx, labels=None, force=False, wc=None):
1883 def merge(ctx, labels=None, force=False, wc=None):
1880 """Merge another topological branch into the working copy.
1884 """Merge another topological branch into the working copy.
1881
1885
1882 force = whether the merge was run with 'merge --force' (deprecated)
1886 force = whether the merge was run with 'merge --force' (deprecated)
1883 """
1887 """
1884
1888
1885 return update(
1889 return update(
1886 ctx.repo(),
1890 ctx.repo(),
1887 ctx.rev(),
1891 ctx.rev(),
1888 labels=labels,
1892 labels=labels,
1889 branchmerge=True,
1893 branchmerge=True,
1890 force=force,
1894 force=force,
1891 mergeforce=force,
1895 mergeforce=force,
1892 wc=wc,
1896 wc=wc,
1893 )
1897 )
1894
1898
1895
1899
1896 def clean_update(ctx, wc=None):
1900 def clean_update(ctx, wc=None):
1897 """Do a clean update to the given commit.
1901 """Do a clean update to the given commit.
1898
1902
1899 This involves updating to the commit and discarding any changes in the
1903 This involves updating to the commit and discarding any changes in the
1900 working copy.
1904 working copy.
1901 """
1905 """
1902 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
1906 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
1903
1907
1904
1908
1905 def revert_to(ctx, matcher=None, wc=None):
1909 def revert_to(ctx, matcher=None, wc=None):
1906 """Revert the working copy to the given commit.
1910 """Revert the working copy to the given commit.
1907
1911
1908 The working copy will keep its current parent(s) but its content will
1912 The working copy will keep its current parent(s) but its content will
1909 be the same as in the given commit.
1913 be the same as in the given commit.
1910 """
1914 """
1911
1915
1912 return update(
1916 return update(
1913 ctx.repo(),
1917 ctx.repo(),
1914 ctx.rev(),
1918 ctx.rev(),
1915 branchmerge=False,
1919 branchmerge=False,
1916 force=True,
1920 force=True,
1917 updatedirstate=False,
1921 updatedirstate=False,
1918 matcher=matcher,
1922 matcher=matcher,
1919 wc=wc,
1923 wc=wc,
1920 )
1924 )
1921
1925
1922
1926
1923 def graft(
1927 def graft(
1924 repo,
1928 repo,
1925 ctx,
1929 ctx,
1926 base=None,
1930 base=None,
1927 labels=None,
1931 labels=None,
1928 keepparent=False,
1932 keepparent=False,
1929 keepconflictparent=False,
1933 keepconflictparent=False,
1930 wctx=None,
1934 wctx=None,
1931 ):
1935 ):
1932 """Do a graft-like merge.
1936 """Do a graft-like merge.
1933
1937
1934 This is a merge where the merge ancestor is chosen such that one
1938 This is a merge where the merge ancestor is chosen such that one
1935 or more changesets are grafted onto the current changeset. In
1939 or more changesets are grafted onto the current changeset. In
1936 addition to the merge, this fixes up the dirstate to include only
1940 addition to the merge, this fixes up the dirstate to include only
1937 a single parent (if keepparent is False) and tries to duplicate any
1941 a single parent (if keepparent is False) and tries to duplicate any
1938 renames/copies appropriately.
1942 renames/copies appropriately.
1939
1943
1940 ctx - changeset to rebase
1944 ctx - changeset to rebase
1941 base - merge base, or ctx.p1() if not specified
1945 base - merge base, or ctx.p1() if not specified
1942 labels - merge labels eg ['local', 'graft']
1946 labels - merge labels eg ['local', 'graft']
1943 keepparent - keep second parent if any
1947 keepparent - keep second parent if any
1944 keepconflictparent - if unresolved, keep parent used for the merge
1948 keepconflictparent - if unresolved, keep parent used for the merge
1945
1949
1946 """
1950 """
1947 # If we're grafting a descendant onto an ancestor, be sure to pass
1951 # If we're grafting a descendant onto an ancestor, be sure to pass
1948 # mergeancestor=True to update. This does two things: 1) allows the merge if
1952 # mergeancestor=True to update. This does two things: 1) allows the merge if
1949 # the destination is the same as the parent of the ctx (so we can use graft
1953 # the destination is the same as the parent of the ctx (so we can use graft
1950 # to copy commits), and 2) informs update that the incoming changes are
1954 # to copy commits), and 2) informs update that the incoming changes are
1951 # newer than the destination so it doesn't prompt about "remote changed foo
1955 # newer than the destination so it doesn't prompt about "remote changed foo
1952 # which local deleted".
1956 # which local deleted".
1953 # We also pass mergeancestor=True when base is the same revision as p1. 2)
1957 # We also pass mergeancestor=True when base is the same revision as p1. 2)
1954 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
1958 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
1955 wctx = wctx or repo[None]
1959 wctx = wctx or repo[None]
1956 pctx = wctx.p1()
1960 pctx = wctx.p1()
1957 base = base or ctx.p1()
1961 base = base or ctx.p1()
1958 mergeancestor = (
1962 mergeancestor = (
1959 repo.changelog.isancestor(pctx.node(), ctx.node())
1963 repo.changelog.isancestor(pctx.node(), ctx.node())
1960 or pctx.rev() == base.rev()
1964 or pctx.rev() == base.rev()
1961 )
1965 )
1962
1966
1963 stats = update(
1967 stats = update(
1964 repo,
1968 repo,
1965 ctx.node(),
1969 ctx.node(),
1966 True,
1970 True,
1967 True,
1971 True,
1968 base.node(),
1972 base.node(),
1969 mergeancestor=mergeancestor,
1973 mergeancestor=mergeancestor,
1970 labels=labels,
1974 labels=labels,
1971 wc=wctx,
1975 wc=wctx,
1972 )
1976 )
1973
1977
1974 if keepconflictparent and stats.unresolvedcount:
1978 if keepconflictparent and stats.unresolvedcount:
1975 pother = ctx.node()
1979 pother = ctx.node()
1976 else:
1980 else:
1977 pother = nullid
1981 pother = nullid
1978 parents = ctx.parents()
1982 parents = ctx.parents()
1979 if keepparent and len(parents) == 2 and base in parents:
1983 if keepparent and len(parents) == 2 and base in parents:
1980 parents.remove(base)
1984 parents.remove(base)
1981 pother = parents[0].node()
1985 pother = parents[0].node()
1982 # Never set both parents equal to each other
1986 # Never set both parents equal to each other
1983 if pother == pctx.node():
1987 if pother == pctx.node():
1984 pother = nullid
1988 pother = nullid
1985
1989
1986 if wctx.isinmemory():
1990 if wctx.isinmemory():
1987 wctx.setparents(pctx.node(), pother)
1991 wctx.setparents(pctx.node(), pother)
1988 # fix up dirstate for copies and renames
1992 # fix up dirstate for copies and renames
1989 copies.graftcopies(wctx, ctx, base)
1993 copies.graftcopies(wctx, ctx, base)
1990 else:
1994 else:
1991 with repo.dirstate.parentchange():
1995 with repo.dirstate.parentchange():
1992 repo.setparents(pctx.node(), pother)
1996 repo.setparents(pctx.node(), pother)
1993 repo.dirstate.write(repo.currenttransaction())
1997 repo.dirstate.write(repo.currenttransaction())
1994 # fix up dirstate for copies and renames
1998 # fix up dirstate for copies and renames
1995 copies.graftcopies(wctx, ctx, base)
1999 copies.graftcopies(wctx, ctx, base)
1996 return stats
2000 return stats
1997
2001
1998
2002
1999 def purge(
2003 def purge(
2000 repo,
2004 repo,
2001 matcher,
2005 matcher,
2002 unknown=True,
2006 unknown=True,
2003 ignored=False,
2007 ignored=False,
2004 removeemptydirs=True,
2008 removeemptydirs=True,
2005 removefiles=True,
2009 removefiles=True,
2006 abortonerror=False,
2010 abortonerror=False,
2007 noop=False,
2011 noop=False,
2008 ):
2012 ):
2009 """Purge the working directory of untracked files.
2013 """Purge the working directory of untracked files.
2010
2014
2011 ``matcher`` is a matcher configured to scan the working directory -
2015 ``matcher`` is a matcher configured to scan the working directory -
2012 potentially a subset.
2016 potentially a subset.
2013
2017
2014 ``unknown`` controls whether unknown files should be purged.
2018 ``unknown`` controls whether unknown files should be purged.
2015
2019
2016 ``ignored`` controls whether ignored files should be purged.
2020 ``ignored`` controls whether ignored files should be purged.
2017
2021
2018 ``removeemptydirs`` controls whether empty directories should be removed.
2022 ``removeemptydirs`` controls whether empty directories should be removed.
2019
2023
2020 ``removefiles`` controls whether files are removed.
2024 ``removefiles`` controls whether files are removed.
2021
2025
2022 ``abortonerror`` causes an exception to be raised if an error occurs
2026 ``abortonerror`` causes an exception to be raised if an error occurs
2023 deleting a file or directory.
2027 deleting a file or directory.
2024
2028
2025 ``noop`` controls whether to actually remove files. If not defined, actions
2029 ``noop`` controls whether to actually remove files. If not defined, actions
2026 will be taken.
2030 will be taken.
2027
2031
2028 Returns an iterable of relative paths in the working directory that were
2032 Returns an iterable of relative paths in the working directory that were
2029 or would be removed.
2033 or would be removed.
2030 """
2034 """
2031
2035
2032 def remove(removefn, path):
2036 def remove(removefn, path):
2033 try:
2037 try:
2034 removefn(path)
2038 removefn(path)
2035 except OSError:
2039 except OSError:
2036 m = _(b'%s cannot be removed') % path
2040 m = _(b'%s cannot be removed') % path
2037 if abortonerror:
2041 if abortonerror:
2038 raise error.Abort(m)
2042 raise error.Abort(m)
2039 else:
2043 else:
2040 repo.ui.warn(_(b'warning: %s\n') % m)
2044 repo.ui.warn(_(b'warning: %s\n') % m)
2041
2045
2042 # There's no API to copy a matcher. So mutate the passed matcher and
2046 # There's no API to copy a matcher. So mutate the passed matcher and
2043 # restore it when we're done.
2047 # restore it when we're done.
2044 oldtraversedir = matcher.traversedir
2048 oldtraversedir = matcher.traversedir
2045
2049
2046 res = []
2050 res = []
2047
2051
2048 try:
2052 try:
2049 if removeemptydirs:
2053 if removeemptydirs:
2050 directories = []
2054 directories = []
2051 matcher.traversedir = directories.append
2055 matcher.traversedir = directories.append
2052
2056
2053 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2057 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2054
2058
2055 if removefiles:
2059 if removefiles:
2056 for f in sorted(status.unknown + status.ignored):
2060 for f in sorted(status.unknown + status.ignored):
2057 if not noop:
2061 if not noop:
2058 repo.ui.note(_(b'removing file %s\n') % f)
2062 repo.ui.note(_(b'removing file %s\n') % f)
2059 remove(repo.wvfs.unlink, f)
2063 remove(repo.wvfs.unlink, f)
2060 res.append(f)
2064 res.append(f)
2061
2065
2062 if removeemptydirs:
2066 if removeemptydirs:
2063 for f in sorted(directories, reverse=True):
2067 for f in sorted(directories, reverse=True):
2064 if matcher(f) and not repo.wvfs.listdir(f):
2068 if matcher(f) and not repo.wvfs.listdir(f):
2065 if not noop:
2069 if not noop:
2066 repo.ui.note(_(b'removing directory %s\n') % f)
2070 repo.ui.note(_(b'removing directory %s\n') % f)
2067 remove(repo.wvfs.rmdir, f)
2071 remove(repo.wvfs.rmdir, f)
2068 res.append(f)
2072 res.append(f)
2069
2073
2070 return res
2074 return res
2071
2075
2072 finally:
2076 finally:
2073 matcher.traversedir = oldtraversedir
2077 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now