##// END OF EJS Templates
merge: pass mergeresult obj instead of actions in _filternarrowactions()...
Pulkit Goyal -
r45842:e7196f1d default
parent child Browse files
Show More
@@ -1,2227 +1,2227
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, mresult):
385 def checkpathconflicts(repo, wctx, mctx, mresult):
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 mresult.actions.items():
410 for f, (m, args, msg) in mresult.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 mresult.actions and mresult.actions[p][0] in (
447 if p in mresult.actions and mresult.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(mresult.actions.keys()))
462 pnew = util.safename(p, ctxname, wctx, set(mresult.actions.keys()))
463 porig = wctx[p].copysource() or p
463 porig = wctx[p].copysource() or p
464 mresult.addfile(
464 mresult.addfile(
465 pnew,
465 pnew,
466 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
466 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
467 (p, porig),
467 (p, porig),
468 b'local path conflict',
468 b'local path conflict',
469 )
469 )
470 mresult.addfile(
470 mresult.addfile(
471 p,
471 p,
472 mergestatemod.ACTION_PATH_CONFLICT,
472 mergestatemod.ACTION_PATH_CONFLICT,
473 (pnew, b'l'),
473 (pnew, b'l'),
474 b'path conflict',
474 b'path conflict',
475 )
475 )
476
476
477 if remoteconflicts:
477 if remoteconflicts:
478 # Check if all files in the conflicting directories have been removed.
478 # Check if all files in the conflicting directories have been removed.
479 ctxname = bytes(mctx).rstrip(b'+')
479 ctxname = bytes(mctx).rstrip(b'+')
480 for f, p in _filesindirs(repo, mf, remoteconflicts):
480 for f, p in _filesindirs(repo, mf, remoteconflicts):
481 if f not in deletedfiles:
481 if f not in deletedfiles:
482 m, args, msg = mresult.actions[p]
482 m, args, msg = mresult.actions[p]
483 pnew = util.safename(
483 pnew = util.safename(
484 p, ctxname, wctx, set(mresult.actions.keys())
484 p, ctxname, wctx, set(mresult.actions.keys())
485 )
485 )
486 if m in (
486 if m in (
487 mergestatemod.ACTION_DELETED_CHANGED,
487 mergestatemod.ACTION_DELETED_CHANGED,
488 mergestatemod.ACTION_MERGE,
488 mergestatemod.ACTION_MERGE,
489 ):
489 ):
490 # Action was merge, just update target.
490 # Action was merge, just update target.
491 mresult.addfile(pnew, m, args, msg)
491 mresult.addfile(pnew, m, args, msg)
492 else:
492 else:
493 # Action was create, change to renamed get action.
493 # Action was create, change to renamed get action.
494 fl = args[0]
494 fl = args[0]
495 mresult.addfile(
495 mresult.addfile(
496 pnew,
496 pnew,
497 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
497 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
498 (p, fl),
498 (p, fl),
499 b'remote path conflict',
499 b'remote path conflict',
500 )
500 )
501 mresult.addfile(
501 mresult.addfile(
502 p,
502 p,
503 mergestatemod.ACTION_PATH_CONFLICT,
503 mergestatemod.ACTION_PATH_CONFLICT,
504 (pnew, mergestatemod.ACTION_REMOVE),
504 (pnew, mergestatemod.ACTION_REMOVE),
505 b'path conflict',
505 b'path conflict',
506 )
506 )
507 remoteconflicts.remove(p)
507 remoteconflicts.remove(p)
508 break
508 break
509
509
510 if invalidconflicts:
510 if invalidconflicts:
511 for p in invalidconflicts:
511 for p in invalidconflicts:
512 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
512 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
513 raise error.Abort(_(b"destination manifest contains path conflicts"))
513 raise error.Abort(_(b"destination manifest contains path conflicts"))
514
514
515
515
516 def _filternarrowactions(narrowmatch, branchmerge, actions):
516 def _filternarrowactions(narrowmatch, branchmerge, mresult):
517 """
517 """
518 Filters out actions that can ignored because the repo is narrowed.
518 Filters out actions that can ignored because the repo is narrowed.
519
519
520 Raise an exception if the merge cannot be completed because the repo is
520 Raise an exception if the merge cannot be completed because the repo is
521 narrowed.
521 narrowed.
522 """
522 """
523 nooptypes = {b'k'} # TODO: handle with nonconflicttypes
523 nooptypes = {b'k'} # TODO: handle with nonconflicttypes
524 nonconflicttypes = set(b'a am c cm f g gs r e'.split())
524 nonconflicttypes = set(b'a am c cm f g gs r e'.split())
525 # We mutate the items in the dict during iteration, so iterate
525 # We mutate the items in the dict during iteration, so iterate
526 # over a copy.
526 # over a copy.
527 for f, action in list(actions.items()):
527 for f, action in list(mresult.actions.items()):
528 if narrowmatch(f):
528 if narrowmatch(f):
529 pass
529 pass
530 elif not branchmerge:
530 elif not branchmerge:
531 del actions[f] # just updating, ignore changes outside clone
531 mresult.removefile(f) # just updating, ignore changes outside clone
532 elif action[0] in nooptypes:
532 elif action[0] in nooptypes:
533 del actions[f] # merge does not affect file
533 mresult.removefile(f) # merge does not affect file
534 elif action[0] in nonconflicttypes:
534 elif action[0] in nonconflicttypes:
535 raise error.Abort(
535 raise error.Abort(
536 _(
536 _(
537 b'merge affects file \'%s\' outside narrow, '
537 b'merge affects file \'%s\' outside narrow, '
538 b'which is not yet supported'
538 b'which is not yet supported'
539 )
539 )
540 % f,
540 % f,
541 hint=_(b'merging in the other direction may work'),
541 hint=_(b'merging in the other direction may work'),
542 )
542 )
543 else:
543 else:
544 raise error.Abort(
544 raise error.Abort(
545 _(b'conflict in file \'%s\' is outside narrow clone') % f
545 _(b'conflict in file \'%s\' is outside narrow clone') % f
546 )
546 )
547
547
548
548
549 class mergeresult(object):
549 class mergeresult(object):
550 ''''An object representing result of merging manifests.
550 ''''An object representing result of merging manifests.
551
551
552 It has information about what actions need to be performed on dirstate
552 It has information about what actions need to be performed on dirstate
553 mapping of divergent renames and other such cases. '''
553 mapping of divergent renames and other such cases. '''
554
554
555 def __init__(self):
555 def __init__(self):
556 """
556 """
557 actions: dict of filename as keys and action related info as values
557 actions: dict of filename as keys and action related info as values
558 diverge: mapping of source name -> list of dest name for
558 diverge: mapping of source name -> list of dest name for
559 divergent renames
559 divergent renames
560 renamedelete: mapping of source name -> list of destinations for files
560 renamedelete: mapping of source name -> list of destinations for files
561 deleted on one side and renamed on other.
561 deleted on one side and renamed on other.
562 commitinfo: dict containing data which should be used on commit
562 commitinfo: dict containing data which should be used on commit
563 contains a filename -> info mapping
563 contains a filename -> info mapping
564 """
564 """
565 self._actions = {}
565 self._actions = {}
566 self._diverge = {}
566 self._diverge = {}
567 self._renamedelete = {}
567 self._renamedelete = {}
568 self._commitinfo = {}
568 self._commitinfo = {}
569
569
570 def updatevalues(self, diverge, renamedelete, commitinfo):
570 def updatevalues(self, diverge, renamedelete, commitinfo):
571 self._diverge = diverge
571 self._diverge = diverge
572 self._renamedelete = renamedelete
572 self._renamedelete = renamedelete
573 self._commitinfo = commitinfo
573 self._commitinfo = commitinfo
574
574
575 def addfile(self, filename, action, data, message):
575 def addfile(self, filename, action, data, message):
576 """ adds a new file to the mergeresult object
576 """ adds a new file to the mergeresult object
577
577
578 filename: file which we are adding
578 filename: file which we are adding
579 action: one of mergestatemod.ACTION_*
579 action: one of mergestatemod.ACTION_*
580 data: a tuple of information like fctx and ctx related to this merge
580 data: a tuple of information like fctx and ctx related to this merge
581 message: a message about the merge
581 message: a message about the merge
582 """
582 """
583 self._actions[filename] = (action, data, message)
583 self._actions[filename] = (action, data, message)
584
584
585 def removefile(self, filename):
585 def removefile(self, filename):
586 """ removes a file from the mergeresult object as the file might
586 """ removes a file from the mergeresult object as the file might
587 not merging anymore """
587 not merging anymore """
588 del self._actions[filename]
588 del self._actions[filename]
589
589
590 @property
590 @property
591 def actions(self):
591 def actions(self):
592 return self._actions
592 return self._actions
593
593
594 @property
594 @property
595 def diverge(self):
595 def diverge(self):
596 return self._diverge
596 return self._diverge
597
597
598 @property
598 @property
599 def renamedelete(self):
599 def renamedelete(self):
600 return self._renamedelete
600 return self._renamedelete
601
601
602 @property
602 @property
603 def commitinfo(self):
603 def commitinfo(self):
604 return self._commitinfo
604 return self._commitinfo
605
605
606 @property
606 @property
607 def actionsdict(self):
607 def actionsdict(self):
608 """ returns a dictionary of actions to be perfomed with action as key
608 """ returns a dictionary of actions to be perfomed with action as key
609 and a list of files and related arguments as values """
609 and a list of files and related arguments as values """
610 # Convert to dictionary-of-lists format
610 # Convert to dictionary-of-lists format
611 actions = emptyactions()
611 actions = emptyactions()
612 for f, (m, args, msg) in pycompat.iteritems(self._actions):
612 for f, (m, args, msg) in pycompat.iteritems(self._actions):
613 if m not in actions:
613 if m not in actions:
614 actions[m] = []
614 actions[m] = []
615 actions[m].append((f, args, msg))
615 actions[m].append((f, args, msg))
616
616
617 return actions
617 return actions
618
618
619 def setactions(self, actions):
619 def setactions(self, actions):
620 self._actions = actions
620 self._actions = actions
621
621
622 def hasconflicts(self):
622 def hasconflicts(self):
623 """ tells whether this merge resulted in some actions which can
623 """ tells whether this merge resulted in some actions which can
624 result in conflicts or not """
624 result in conflicts or not """
625 for _f, (m, _unused, _unused) in pycompat.iteritems(self._actions):
625 for _f, (m, _unused, _unused) in pycompat.iteritems(self._actions):
626 if m not in (
626 if m not in (
627 mergestatemod.ACTION_GET,
627 mergestatemod.ACTION_GET,
628 mergestatemod.ACTION_KEEP,
628 mergestatemod.ACTION_KEEP,
629 mergestatemod.ACTION_EXEC,
629 mergestatemod.ACTION_EXEC,
630 mergestatemod.ACTION_REMOVE,
630 mergestatemod.ACTION_REMOVE,
631 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
631 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
632 ):
632 ):
633 return True
633 return True
634
634
635 return False
635 return False
636
636
637
637
638 def manifestmerge(
638 def manifestmerge(
639 repo,
639 repo,
640 wctx,
640 wctx,
641 p2,
641 p2,
642 pa,
642 pa,
643 branchmerge,
643 branchmerge,
644 force,
644 force,
645 matcher,
645 matcher,
646 acceptremote,
646 acceptremote,
647 followcopies,
647 followcopies,
648 forcefulldiff=False,
648 forcefulldiff=False,
649 ):
649 ):
650 """
650 """
651 Merge wctx and p2 with ancestor pa and generate merge action list
651 Merge wctx and p2 with ancestor pa and generate merge action list
652
652
653 branchmerge and force are as passed in to update
653 branchmerge and force are as passed in to update
654 matcher = matcher to filter file lists
654 matcher = matcher to filter file lists
655 acceptremote = accept the incoming changes without prompting
655 acceptremote = accept the incoming changes without prompting
656
656
657 Returns an object of mergeresult class
657 Returns an object of mergeresult class
658 """
658 """
659 mresult = mergeresult()
659 mresult = mergeresult()
660 if matcher is not None and matcher.always():
660 if matcher is not None and matcher.always():
661 matcher = None
661 matcher = None
662
662
663 # manifests fetched in order are going to be faster, so prime the caches
663 # manifests fetched in order are going to be faster, so prime the caches
664 [
664 [
665 x.manifest()
665 x.manifest()
666 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
666 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
667 ]
667 ]
668
668
669 branch_copies1 = copies.branch_copies()
669 branch_copies1 = copies.branch_copies()
670 branch_copies2 = copies.branch_copies()
670 branch_copies2 = copies.branch_copies()
671 diverge = {}
671 diverge = {}
672 # information from merge which is needed at commit time
672 # information from merge which is needed at commit time
673 # for example choosing filelog of which parent to commit
673 # for example choosing filelog of which parent to commit
674 # TODO: use specific constants in future for this mapping
674 # TODO: use specific constants in future for this mapping
675 commitinfo = {}
675 commitinfo = {}
676 if followcopies:
676 if followcopies:
677 branch_copies1, branch_copies2, diverge = copies.mergecopies(
677 branch_copies1, branch_copies2, diverge = copies.mergecopies(
678 repo, wctx, p2, pa
678 repo, wctx, p2, pa
679 )
679 )
680
680
681 boolbm = pycompat.bytestr(bool(branchmerge))
681 boolbm = pycompat.bytestr(bool(branchmerge))
682 boolf = pycompat.bytestr(bool(force))
682 boolf = pycompat.bytestr(bool(force))
683 boolm = pycompat.bytestr(bool(matcher))
683 boolm = pycompat.bytestr(bool(matcher))
684 repo.ui.note(_(b"resolving manifests\n"))
684 repo.ui.note(_(b"resolving manifests\n"))
685 repo.ui.debug(
685 repo.ui.debug(
686 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
686 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
687 )
687 )
688 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
688 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
689
689
690 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
690 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
691 copied1 = set(branch_copies1.copy.values())
691 copied1 = set(branch_copies1.copy.values())
692 copied1.update(branch_copies1.movewithdir.values())
692 copied1.update(branch_copies1.movewithdir.values())
693 copied2 = set(branch_copies2.copy.values())
693 copied2 = set(branch_copies2.copy.values())
694 copied2.update(branch_copies2.movewithdir.values())
694 copied2.update(branch_copies2.movewithdir.values())
695
695
696 if b'.hgsubstate' in m1 and wctx.rev() is None:
696 if b'.hgsubstate' in m1 and wctx.rev() is None:
697 # Check whether sub state is modified, and overwrite the manifest
697 # Check whether sub state is modified, and overwrite the manifest
698 # to flag the change. If wctx is a committed revision, we shouldn't
698 # to flag the change. If wctx is a committed revision, we shouldn't
699 # care for the dirty state of the working directory.
699 # care for the dirty state of the working directory.
700 if any(wctx.sub(s).dirty() for s in wctx.substate):
700 if any(wctx.sub(s).dirty() for s in wctx.substate):
701 m1[b'.hgsubstate'] = modifiednodeid
701 m1[b'.hgsubstate'] = modifiednodeid
702
702
703 # Don't use m2-vs-ma optimization if:
703 # Don't use m2-vs-ma optimization if:
704 # - ma is the same as m1 or m2, which we're just going to diff again later
704 # - ma is the same as m1 or m2, which we're just going to diff again later
705 # - The caller specifically asks for a full diff, which is useful during bid
705 # - The caller specifically asks for a full diff, which is useful during bid
706 # merge.
706 # merge.
707 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
707 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
708 # Identify which files are relevant to the merge, so we can limit the
708 # Identify which files are relevant to the merge, so we can limit the
709 # total m1-vs-m2 diff to just those files. This has significant
709 # total m1-vs-m2 diff to just those files. This has significant
710 # performance benefits in large repositories.
710 # performance benefits in large repositories.
711 relevantfiles = set(ma.diff(m2).keys())
711 relevantfiles = set(ma.diff(m2).keys())
712
712
713 # For copied and moved files, we need to add the source file too.
713 # For copied and moved files, we need to add the source file too.
714 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
714 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
715 if copyvalue in relevantfiles:
715 if copyvalue in relevantfiles:
716 relevantfiles.add(copykey)
716 relevantfiles.add(copykey)
717 for movedirkey in branch_copies1.movewithdir:
717 for movedirkey in branch_copies1.movewithdir:
718 relevantfiles.add(movedirkey)
718 relevantfiles.add(movedirkey)
719 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
719 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
720 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
720 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
721
721
722 diff = m1.diff(m2, match=matcher)
722 diff = m1.diff(m2, match=matcher)
723
723
724 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
724 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
725 if n1 and n2: # file exists on both local and remote side
725 if n1 and n2: # file exists on both local and remote side
726 if f not in ma:
726 if f not in ma:
727 # TODO: what if they're renamed from different sources?
727 # TODO: what if they're renamed from different sources?
728 fa = branch_copies1.copy.get(
728 fa = branch_copies1.copy.get(
729 f, None
729 f, None
730 ) or branch_copies2.copy.get(f, None)
730 ) or branch_copies2.copy.get(f, None)
731 if fa is not None:
731 if fa is not None:
732 mresult.addfile(
732 mresult.addfile(
733 f,
733 f,
734 mergestatemod.ACTION_MERGE,
734 mergestatemod.ACTION_MERGE,
735 (f, f, fa, False, pa.node()),
735 (f, f, fa, False, pa.node()),
736 b'both renamed from %s' % fa,
736 b'both renamed from %s' % fa,
737 )
737 )
738 else:
738 else:
739 mresult.addfile(
739 mresult.addfile(
740 f,
740 f,
741 mergestatemod.ACTION_MERGE,
741 mergestatemod.ACTION_MERGE,
742 (f, f, None, False, pa.node()),
742 (f, f, None, False, pa.node()),
743 b'both created',
743 b'both created',
744 )
744 )
745 else:
745 else:
746 a = ma[f]
746 a = ma[f]
747 fla = ma.flags(f)
747 fla = ma.flags(f)
748 nol = b'l' not in fl1 + fl2 + fla
748 nol = b'l' not in fl1 + fl2 + fla
749 if n2 == a and fl2 == fla:
749 if n2 == a and fl2 == fla:
750 mresult.addfile(
750 mresult.addfile(
751 f, mergestatemod.ACTION_KEEP, (), b'remote unchanged',
751 f, mergestatemod.ACTION_KEEP, (), b'remote unchanged',
752 )
752 )
753 elif n1 == a and fl1 == fla: # local unchanged - use remote
753 elif n1 == a and fl1 == fla: # local unchanged - use remote
754 if n1 == n2: # optimization: keep local content
754 if n1 == n2: # optimization: keep local content
755 mresult.addfile(
755 mresult.addfile(
756 f,
756 f,
757 mergestatemod.ACTION_EXEC,
757 mergestatemod.ACTION_EXEC,
758 (fl2,),
758 (fl2,),
759 b'update permissions',
759 b'update permissions',
760 )
760 )
761 else:
761 else:
762 mresult.addfile(
762 mresult.addfile(
763 f,
763 f,
764 mergestatemod.ACTION_GET,
764 mergestatemod.ACTION_GET,
765 (fl2, False),
765 (fl2, False),
766 b'remote is newer',
766 b'remote is newer',
767 )
767 )
768 if branchmerge:
768 if branchmerge:
769 commitinfo[f] = b'other'
769 commitinfo[f] = b'other'
770 elif nol and n2 == a: # remote only changed 'x'
770 elif nol and n2 == a: # remote only changed 'x'
771 mresult.addfile(
771 mresult.addfile(
772 f,
772 f,
773 mergestatemod.ACTION_EXEC,
773 mergestatemod.ACTION_EXEC,
774 (fl2,),
774 (fl2,),
775 b'update permissions',
775 b'update permissions',
776 )
776 )
777 elif nol and n1 == a: # local only changed 'x'
777 elif nol and n1 == a: # local only changed 'x'
778 mresult.addfile(
778 mresult.addfile(
779 f,
779 f,
780 mergestatemod.ACTION_GET,
780 mergestatemod.ACTION_GET,
781 (fl1, False),
781 (fl1, False),
782 b'remote is newer',
782 b'remote is newer',
783 )
783 )
784 if branchmerge:
784 if branchmerge:
785 commitinfo[f] = b'other'
785 commitinfo[f] = b'other'
786 else: # both changed something
786 else: # both changed something
787 mresult.addfile(
787 mresult.addfile(
788 f,
788 f,
789 mergestatemod.ACTION_MERGE,
789 mergestatemod.ACTION_MERGE,
790 (f, f, f, False, pa.node()),
790 (f, f, f, False, pa.node()),
791 b'versions differ',
791 b'versions differ',
792 )
792 )
793 elif n1: # file exists only on local side
793 elif n1: # file exists only on local side
794 if f in copied2:
794 if f in copied2:
795 pass # we'll deal with it on m2 side
795 pass # we'll deal with it on m2 side
796 elif (
796 elif (
797 f in branch_copies1.movewithdir
797 f in branch_copies1.movewithdir
798 ): # directory rename, move local
798 ): # directory rename, move local
799 f2 = branch_copies1.movewithdir[f]
799 f2 = branch_copies1.movewithdir[f]
800 if f2 in m2:
800 if f2 in m2:
801 mresult.addfile(
801 mresult.addfile(
802 f2,
802 f2,
803 mergestatemod.ACTION_MERGE,
803 mergestatemod.ACTION_MERGE,
804 (f, f2, None, True, pa.node()),
804 (f, f2, None, True, pa.node()),
805 b'remote directory rename, both created',
805 b'remote directory rename, both created',
806 )
806 )
807 else:
807 else:
808 mresult.addfile(
808 mresult.addfile(
809 f2,
809 f2,
810 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
810 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
811 (f, fl1),
811 (f, fl1),
812 b'remote directory rename - move from %s' % f,
812 b'remote directory rename - move from %s' % f,
813 )
813 )
814 elif f in branch_copies1.copy:
814 elif f in branch_copies1.copy:
815 f2 = branch_copies1.copy[f]
815 f2 = branch_copies1.copy[f]
816 mresult.addfile(
816 mresult.addfile(
817 f,
817 f,
818 mergestatemod.ACTION_MERGE,
818 mergestatemod.ACTION_MERGE,
819 (f, f2, f2, False, pa.node()),
819 (f, f2, f2, False, pa.node()),
820 b'local copied/moved from %s' % f2,
820 b'local copied/moved from %s' % f2,
821 )
821 )
822 elif f in ma: # clean, a different, no remote
822 elif f in ma: # clean, a different, no remote
823 if n1 != ma[f]:
823 if n1 != ma[f]:
824 if acceptremote:
824 if acceptremote:
825 mresult.addfile(
825 mresult.addfile(
826 f,
826 f,
827 mergestatemod.ACTION_REMOVE,
827 mergestatemod.ACTION_REMOVE,
828 None,
828 None,
829 b'remote delete',
829 b'remote delete',
830 )
830 )
831 else:
831 else:
832 mresult.addfile(
832 mresult.addfile(
833 f,
833 f,
834 mergestatemod.ACTION_CHANGED_DELETED,
834 mergestatemod.ACTION_CHANGED_DELETED,
835 (f, None, f, False, pa.node()),
835 (f, None, f, False, pa.node()),
836 b'prompt changed/deleted',
836 b'prompt changed/deleted',
837 )
837 )
838 elif n1 == addednodeid:
838 elif n1 == addednodeid:
839 # This file was locally added. We should forget it instead of
839 # This file was locally added. We should forget it instead of
840 # deleting it.
840 # deleting it.
841 mresult.addfile(
841 mresult.addfile(
842 f, mergestatemod.ACTION_FORGET, None, b'remote deleted',
842 f, mergestatemod.ACTION_FORGET, None, b'remote deleted',
843 )
843 )
844 else:
844 else:
845 mresult.addfile(
845 mresult.addfile(
846 f, mergestatemod.ACTION_REMOVE, None, b'other deleted',
846 f, mergestatemod.ACTION_REMOVE, None, b'other deleted',
847 )
847 )
848 elif n2: # file exists only on remote side
848 elif n2: # file exists only on remote side
849 if f in copied1:
849 if f in copied1:
850 pass # we'll deal with it on m1 side
850 pass # we'll deal with it on m1 side
851 elif f in branch_copies2.movewithdir:
851 elif f in branch_copies2.movewithdir:
852 f2 = branch_copies2.movewithdir[f]
852 f2 = branch_copies2.movewithdir[f]
853 if f2 in m1:
853 if f2 in m1:
854 mresult.addfile(
854 mresult.addfile(
855 f2,
855 f2,
856 mergestatemod.ACTION_MERGE,
856 mergestatemod.ACTION_MERGE,
857 (f2, f, None, False, pa.node()),
857 (f2, f, None, False, pa.node()),
858 b'local directory rename, both created',
858 b'local directory rename, both created',
859 )
859 )
860 else:
860 else:
861 mresult.addfile(
861 mresult.addfile(
862 f2,
862 f2,
863 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
863 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
864 (f, fl2),
864 (f, fl2),
865 b'local directory rename - get from %s' % f,
865 b'local directory rename - get from %s' % f,
866 )
866 )
867 elif f in branch_copies2.copy:
867 elif f in branch_copies2.copy:
868 f2 = branch_copies2.copy[f]
868 f2 = branch_copies2.copy[f]
869 if f2 in m2:
869 if f2 in m2:
870 mresult.addfile(
870 mresult.addfile(
871 f,
871 f,
872 mergestatemod.ACTION_MERGE,
872 mergestatemod.ACTION_MERGE,
873 (f2, f, f2, False, pa.node()),
873 (f2, f, f2, False, pa.node()),
874 b'remote copied from %s' % f2,
874 b'remote copied from %s' % f2,
875 )
875 )
876 else:
876 else:
877 mresult.addfile(
877 mresult.addfile(
878 f,
878 f,
879 mergestatemod.ACTION_MERGE,
879 mergestatemod.ACTION_MERGE,
880 (f2, f, f2, True, pa.node()),
880 (f2, f, f2, True, pa.node()),
881 b'remote moved from %s' % f2,
881 b'remote moved from %s' % f2,
882 )
882 )
883 elif f not in ma:
883 elif f not in ma:
884 # local unknown, remote created: the logic is described by the
884 # local unknown, remote created: the logic is described by the
885 # following table:
885 # following table:
886 #
886 #
887 # force branchmerge different | action
887 # force branchmerge different | action
888 # n * * | create
888 # n * * | create
889 # y n * | create
889 # y n * | create
890 # y y n | create
890 # y y n | create
891 # y y y | merge
891 # y y y | merge
892 #
892 #
893 # Checking whether the files are different is expensive, so we
893 # Checking whether the files are different is expensive, so we
894 # don't do that when we can avoid it.
894 # don't do that when we can avoid it.
895 if not force:
895 if not force:
896 mresult.addfile(
896 mresult.addfile(
897 f,
897 f,
898 mergestatemod.ACTION_CREATED,
898 mergestatemod.ACTION_CREATED,
899 (fl2,),
899 (fl2,),
900 b'remote created',
900 b'remote created',
901 )
901 )
902 elif not branchmerge:
902 elif not branchmerge:
903 mresult.addfile(
903 mresult.addfile(
904 f,
904 f,
905 mergestatemod.ACTION_CREATED,
905 mergestatemod.ACTION_CREATED,
906 (fl2,),
906 (fl2,),
907 b'remote created',
907 b'remote created',
908 )
908 )
909 else:
909 else:
910 mresult.addfile(
910 mresult.addfile(
911 f,
911 f,
912 mergestatemod.ACTION_CREATED_MERGE,
912 mergestatemod.ACTION_CREATED_MERGE,
913 (fl2, pa.node()),
913 (fl2, pa.node()),
914 b'remote created, get or merge',
914 b'remote created, get or merge',
915 )
915 )
916 elif n2 != ma[f]:
916 elif n2 != ma[f]:
917 df = None
917 df = None
918 for d in branch_copies1.dirmove:
918 for d in branch_copies1.dirmove:
919 if f.startswith(d):
919 if f.startswith(d):
920 # new file added in a directory that was moved
920 # new file added in a directory that was moved
921 df = branch_copies1.dirmove[d] + f[len(d) :]
921 df = branch_copies1.dirmove[d] + f[len(d) :]
922 break
922 break
923 if df is not None and df in m1:
923 if df is not None and df in m1:
924 mresult.addfile(
924 mresult.addfile(
925 df,
925 df,
926 mergestatemod.ACTION_MERGE,
926 mergestatemod.ACTION_MERGE,
927 (df, f, f, False, pa.node()),
927 (df, f, f, False, pa.node()),
928 b'local directory rename - respect move '
928 b'local directory rename - respect move '
929 b'from %s' % f,
929 b'from %s' % f,
930 )
930 )
931 elif acceptremote:
931 elif acceptremote:
932 mresult.addfile(
932 mresult.addfile(
933 f,
933 f,
934 mergestatemod.ACTION_CREATED,
934 mergestatemod.ACTION_CREATED,
935 (fl2,),
935 (fl2,),
936 b'remote recreating',
936 b'remote recreating',
937 )
937 )
938 else:
938 else:
939 mresult.addfile(
939 mresult.addfile(
940 f,
940 f,
941 mergestatemod.ACTION_DELETED_CHANGED,
941 mergestatemod.ACTION_DELETED_CHANGED,
942 (None, f, f, False, pa.node()),
942 (None, f, f, False, pa.node()),
943 b'prompt deleted/changed',
943 b'prompt deleted/changed',
944 )
944 )
945
945
946 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
946 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
947 # If we are merging, look for path conflicts.
947 # If we are merging, look for path conflicts.
948 checkpathconflicts(repo, wctx, p2, mresult)
948 checkpathconflicts(repo, wctx, p2, mresult)
949
949
950 narrowmatch = repo.narrowmatch()
950 narrowmatch = repo.narrowmatch()
951 if not narrowmatch.always():
951 if not narrowmatch.always():
952 # Updates "actions" in place
952 # Updates "actions" in place
953 _filternarrowactions(narrowmatch, branchmerge, mresult.actions)
953 _filternarrowactions(narrowmatch, branchmerge, mresult)
954
954
955 renamedelete = branch_copies1.renamedelete
955 renamedelete = branch_copies1.renamedelete
956 renamedelete.update(branch_copies2.renamedelete)
956 renamedelete.update(branch_copies2.renamedelete)
957
957
958 mresult.updatevalues(diverge, renamedelete, commitinfo)
958 mresult.updatevalues(diverge, renamedelete, commitinfo)
959 return mresult
959 return mresult
960
960
961
961
962 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
962 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
963 """Resolves false conflicts where the nodeid changed but the content
963 """Resolves false conflicts where the nodeid changed but the content
964 remained the same."""
964 remained the same."""
965 # We force a copy of actions.items() because we're going to mutate
965 # We force a copy of actions.items() because we're going to mutate
966 # actions as we resolve trivial conflicts.
966 # actions as we resolve trivial conflicts.
967 for f, (m, args, msg) in list(actions.items()):
967 for f, (m, args, msg) in list(actions.items()):
968 if (
968 if (
969 m == mergestatemod.ACTION_CHANGED_DELETED
969 m == mergestatemod.ACTION_CHANGED_DELETED
970 and f in ancestor
970 and f in ancestor
971 and not wctx[f].cmp(ancestor[f])
971 and not wctx[f].cmp(ancestor[f])
972 ):
972 ):
973 # local did change but ended up with same content
973 # local did change but ended up with same content
974 actions[f] = mergestatemod.ACTION_REMOVE, None, b'prompt same'
974 actions[f] = mergestatemod.ACTION_REMOVE, None, b'prompt same'
975 elif (
975 elif (
976 m == mergestatemod.ACTION_DELETED_CHANGED
976 m == mergestatemod.ACTION_DELETED_CHANGED
977 and f in ancestor
977 and f in ancestor
978 and not mctx[f].cmp(ancestor[f])
978 and not mctx[f].cmp(ancestor[f])
979 ):
979 ):
980 # remote did change but ended up with same content
980 # remote did change but ended up with same content
981 del actions[f] # don't get = keep local deleted
981 del actions[f] # don't get = keep local deleted
982
982
983
983
984 def calculateupdates(
984 def calculateupdates(
985 repo,
985 repo,
986 wctx,
986 wctx,
987 mctx,
987 mctx,
988 ancestors,
988 ancestors,
989 branchmerge,
989 branchmerge,
990 force,
990 force,
991 acceptremote,
991 acceptremote,
992 followcopies,
992 followcopies,
993 matcher=None,
993 matcher=None,
994 mergeforce=False,
994 mergeforce=False,
995 ):
995 ):
996 """
996 """
997 Calculate the actions needed to merge mctx into wctx using ancestors
997 Calculate the actions needed to merge mctx into wctx using ancestors
998
998
999 Uses manifestmerge() to merge manifest and get list of actions required to
999 Uses manifestmerge() to merge manifest and get list of actions required to
1000 perform for merging two manifests. If there are multiple ancestors, uses bid
1000 perform for merging two manifests. If there are multiple ancestors, uses bid
1001 merge if enabled.
1001 merge if enabled.
1002
1002
1003 Also filters out actions which are unrequired if repository is sparse.
1003 Also filters out actions which are unrequired if repository is sparse.
1004
1004
1005 Returns mergeresult object same as manifestmerge().
1005 Returns mergeresult object same as manifestmerge().
1006 """
1006 """
1007 # Avoid cycle.
1007 # Avoid cycle.
1008 from . import sparse
1008 from . import sparse
1009
1009
1010 mresult = None
1010 mresult = None
1011 if len(ancestors) == 1: # default
1011 if len(ancestors) == 1: # default
1012 mresult = manifestmerge(
1012 mresult = manifestmerge(
1013 repo,
1013 repo,
1014 wctx,
1014 wctx,
1015 mctx,
1015 mctx,
1016 ancestors[0],
1016 ancestors[0],
1017 branchmerge,
1017 branchmerge,
1018 force,
1018 force,
1019 matcher,
1019 matcher,
1020 acceptremote,
1020 acceptremote,
1021 followcopies,
1021 followcopies,
1022 )
1022 )
1023 _checkunknownfiles(repo, wctx, mctx, force, mresult.actions, mergeforce)
1023 _checkunknownfiles(repo, wctx, mctx, force, mresult.actions, mergeforce)
1024
1024
1025 else: # only when merge.preferancestor=* - the default
1025 else: # only when merge.preferancestor=* - the default
1026 repo.ui.note(
1026 repo.ui.note(
1027 _(b"note: merging %s and %s using bids from ancestors %s\n")
1027 _(b"note: merging %s and %s using bids from ancestors %s\n")
1028 % (
1028 % (
1029 wctx,
1029 wctx,
1030 mctx,
1030 mctx,
1031 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1031 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1032 )
1032 )
1033 )
1033 )
1034
1034
1035 # mapping filename to bids (action method to list af actions)
1035 # mapping filename to bids (action method to list af actions)
1036 # {FILENAME1 : BID1, FILENAME2 : BID2}
1036 # {FILENAME1 : BID1, FILENAME2 : BID2}
1037 # BID is another dictionary which contains
1037 # BID is another dictionary which contains
1038 # mapping of following form:
1038 # mapping of following form:
1039 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1039 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1040 fbids = {}
1040 fbids = {}
1041 diverge, renamedelete = None, None
1041 diverge, renamedelete = None, None
1042 for ancestor in ancestors:
1042 for ancestor in ancestors:
1043 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1043 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1044 mresult1 = manifestmerge(
1044 mresult1 = manifestmerge(
1045 repo,
1045 repo,
1046 wctx,
1046 wctx,
1047 mctx,
1047 mctx,
1048 ancestor,
1048 ancestor,
1049 branchmerge,
1049 branchmerge,
1050 force,
1050 force,
1051 matcher,
1051 matcher,
1052 acceptremote,
1052 acceptremote,
1053 followcopies,
1053 followcopies,
1054 forcefulldiff=True,
1054 forcefulldiff=True,
1055 )
1055 )
1056 _checkunknownfiles(
1056 _checkunknownfiles(
1057 repo, wctx, mctx, force, mresult1.actions, mergeforce
1057 repo, wctx, mctx, force, mresult1.actions, mergeforce
1058 )
1058 )
1059
1059
1060 # Track the shortest set of warning on the theory that bid
1060 # Track the shortest set of warning on the theory that bid
1061 # merge will correctly incorporate more information
1061 # merge will correctly incorporate more information
1062 if diverge is None or len(mresult1.diverge) < len(diverge):
1062 if diverge is None or len(mresult1.diverge) < len(diverge):
1063 diverge = mresult1.diverge
1063 diverge = mresult1.diverge
1064 if renamedelete is None or len(renamedelete) < len(
1064 if renamedelete is None or len(renamedelete) < len(
1065 mresult1.renamedelete
1065 mresult1.renamedelete
1066 ):
1066 ):
1067 renamedelete = mresult1.renamedelete
1067 renamedelete = mresult1.renamedelete
1068
1068
1069 for f, a in sorted(pycompat.iteritems(mresult1.actions)):
1069 for f, a in sorted(pycompat.iteritems(mresult1.actions)):
1070 m, args, msg = a
1070 m, args, msg = a
1071 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1071 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1072 if f in fbids:
1072 if f in fbids:
1073 d = fbids[f]
1073 d = fbids[f]
1074 if m in d:
1074 if m in d:
1075 d[m].append(a)
1075 d[m].append(a)
1076 else:
1076 else:
1077 d[m] = [a]
1077 d[m] = [a]
1078 else:
1078 else:
1079 fbids[f] = {m: [a]}
1079 fbids[f] = {m: [a]}
1080
1080
1081 # Call for bids
1081 # Call for bids
1082 # Pick the best bid for each file
1082 # Pick the best bid for each file
1083 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1083 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1084 mresult = mergeresult()
1084 mresult = mergeresult()
1085 for f, bids in sorted(fbids.items()):
1085 for f, bids in sorted(fbids.items()):
1086 # bids is a mapping from action method to list af actions
1086 # bids is a mapping from action method to list af actions
1087 # Consensus?
1087 # Consensus?
1088 if len(bids) == 1: # all bids are the same kind of method
1088 if len(bids) == 1: # all bids are the same kind of method
1089 m, l = list(bids.items())[0]
1089 m, l = list(bids.items())[0]
1090 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1090 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1091 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1091 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1092 mresult.addfile(f, *l[0])
1092 mresult.addfile(f, *l[0])
1093 continue
1093 continue
1094 # If keep is an option, just do it.
1094 # If keep is an option, just do it.
1095 if mergestatemod.ACTION_KEEP in bids:
1095 if mergestatemod.ACTION_KEEP in bids:
1096 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1096 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1097 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1097 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1098 continue
1098 continue
1099 # If there are gets and they all agree [how could they not?], do it.
1099 # If there are gets and they all agree [how could they not?], do it.
1100 if mergestatemod.ACTION_GET in bids:
1100 if mergestatemod.ACTION_GET in bids:
1101 ga0 = bids[mergestatemod.ACTION_GET][0]
1101 ga0 = bids[mergestatemod.ACTION_GET][0]
1102 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1102 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1103 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1103 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1104 mresult.addfile(f, *ga0)
1104 mresult.addfile(f, *ga0)
1105 continue
1105 continue
1106 # TODO: Consider other simple actions such as mode changes
1106 # TODO: Consider other simple actions such as mode changes
1107 # Handle inefficient democrazy.
1107 # Handle inefficient democrazy.
1108 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1108 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1109 for m, l in sorted(bids.items()):
1109 for m, l in sorted(bids.items()):
1110 for _f, args, msg in l:
1110 for _f, args, msg in l:
1111 repo.ui.note(b' %s -> %s\n' % (msg, m))
1111 repo.ui.note(b' %s -> %s\n' % (msg, m))
1112 # Pick random action. TODO: Instead, prompt user when resolving
1112 # Pick random action. TODO: Instead, prompt user when resolving
1113 m, l = list(bids.items())[0]
1113 m, l = list(bids.items())[0]
1114 repo.ui.warn(
1114 repo.ui.warn(
1115 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1115 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1116 )
1116 )
1117 mresult.addfile(f, *l[0])
1117 mresult.addfile(f, *l[0])
1118 continue
1118 continue
1119 repo.ui.note(_(b'end of auction\n\n'))
1119 repo.ui.note(_(b'end of auction\n\n'))
1120 # TODO: think about commitinfo when bid merge is used
1120 # TODO: think about commitinfo when bid merge is used
1121 mresult.updatevalues(diverge, renamedelete, {})
1121 mresult.updatevalues(diverge, renamedelete, {})
1122
1122
1123 if wctx.rev() is None:
1123 if wctx.rev() is None:
1124 fractions = _forgetremoved(wctx, mctx, branchmerge)
1124 fractions = _forgetremoved(wctx, mctx, branchmerge)
1125 mresult.actions.update(fractions)
1125 mresult.actions.update(fractions)
1126
1126
1127 prunedactions = sparse.filterupdatesactions(
1127 prunedactions = sparse.filterupdatesactions(
1128 repo, wctx, mctx, branchmerge, mresult.actions
1128 repo, wctx, mctx, branchmerge, mresult.actions
1129 )
1129 )
1130 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult.actions)
1130 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult.actions)
1131
1131
1132 mresult.setactions(prunedactions)
1132 mresult.setactions(prunedactions)
1133 return mresult
1133 return mresult
1134
1134
1135
1135
1136 def _getcwd():
1136 def _getcwd():
1137 try:
1137 try:
1138 return encoding.getcwd()
1138 return encoding.getcwd()
1139 except OSError as err:
1139 except OSError as err:
1140 if err.errno == errno.ENOENT:
1140 if err.errno == errno.ENOENT:
1141 return None
1141 return None
1142 raise
1142 raise
1143
1143
1144
1144
1145 def batchremove(repo, wctx, actions):
1145 def batchremove(repo, wctx, actions):
1146 """apply removes to the working directory
1146 """apply removes to the working directory
1147
1147
1148 yields tuples for progress updates
1148 yields tuples for progress updates
1149 """
1149 """
1150 verbose = repo.ui.verbose
1150 verbose = repo.ui.verbose
1151 cwd = _getcwd()
1151 cwd = _getcwd()
1152 i = 0
1152 i = 0
1153 for f, args, msg in actions:
1153 for f, args, msg in actions:
1154 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1154 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1155 if verbose:
1155 if verbose:
1156 repo.ui.note(_(b"removing %s\n") % f)
1156 repo.ui.note(_(b"removing %s\n") % f)
1157 wctx[f].audit()
1157 wctx[f].audit()
1158 try:
1158 try:
1159 wctx[f].remove(ignoremissing=True)
1159 wctx[f].remove(ignoremissing=True)
1160 except OSError as inst:
1160 except OSError as inst:
1161 repo.ui.warn(
1161 repo.ui.warn(
1162 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1162 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1163 )
1163 )
1164 if i == 100:
1164 if i == 100:
1165 yield i, f
1165 yield i, f
1166 i = 0
1166 i = 0
1167 i += 1
1167 i += 1
1168 if i > 0:
1168 if i > 0:
1169 yield i, f
1169 yield i, f
1170
1170
1171 if cwd and not _getcwd():
1171 if cwd and not _getcwd():
1172 # cwd was removed in the course of removing files; print a helpful
1172 # cwd was removed in the course of removing files; print a helpful
1173 # warning.
1173 # warning.
1174 repo.ui.warn(
1174 repo.ui.warn(
1175 _(
1175 _(
1176 b"current directory was removed\n"
1176 b"current directory was removed\n"
1177 b"(consider changing to repo root: %s)\n"
1177 b"(consider changing to repo root: %s)\n"
1178 )
1178 )
1179 % repo.root
1179 % repo.root
1180 )
1180 )
1181
1181
1182
1182
1183 def batchget(repo, mctx, wctx, wantfiledata, actions):
1183 def batchget(repo, mctx, wctx, wantfiledata, actions):
1184 """apply gets to the working directory
1184 """apply gets to the working directory
1185
1185
1186 mctx is the context to get from
1186 mctx is the context to get from
1187
1187
1188 Yields arbitrarily many (False, tuple) for progress updates, followed by
1188 Yields arbitrarily many (False, tuple) for progress updates, followed by
1189 exactly one (True, filedata). When wantfiledata is false, filedata is an
1189 exactly one (True, filedata). When wantfiledata is false, filedata is an
1190 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1190 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1191 mtime) of the file f written for each action.
1191 mtime) of the file f written for each action.
1192 """
1192 """
1193 filedata = {}
1193 filedata = {}
1194 verbose = repo.ui.verbose
1194 verbose = repo.ui.verbose
1195 fctx = mctx.filectx
1195 fctx = mctx.filectx
1196 ui = repo.ui
1196 ui = repo.ui
1197 i = 0
1197 i = 0
1198 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1198 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1199 for f, (flags, backup), msg in actions:
1199 for f, (flags, backup), msg in actions:
1200 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1200 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1201 if verbose:
1201 if verbose:
1202 repo.ui.note(_(b"getting %s\n") % f)
1202 repo.ui.note(_(b"getting %s\n") % f)
1203
1203
1204 if backup:
1204 if backup:
1205 # If a file or directory exists with the same name, back that
1205 # If a file or directory exists with the same name, back that
1206 # up. Otherwise, look to see if there is a file that conflicts
1206 # up. Otherwise, look to see if there is a file that conflicts
1207 # with a directory this file is in, and if so, back that up.
1207 # with a directory this file is in, and if so, back that up.
1208 conflicting = f
1208 conflicting = f
1209 if not repo.wvfs.lexists(f):
1209 if not repo.wvfs.lexists(f):
1210 for p in pathutil.finddirs(f):
1210 for p in pathutil.finddirs(f):
1211 if repo.wvfs.isfileorlink(p):
1211 if repo.wvfs.isfileorlink(p):
1212 conflicting = p
1212 conflicting = p
1213 break
1213 break
1214 if repo.wvfs.lexists(conflicting):
1214 if repo.wvfs.lexists(conflicting):
1215 orig = scmutil.backuppath(ui, repo, conflicting)
1215 orig = scmutil.backuppath(ui, repo, conflicting)
1216 util.rename(repo.wjoin(conflicting), orig)
1216 util.rename(repo.wjoin(conflicting), orig)
1217 wfctx = wctx[f]
1217 wfctx = wctx[f]
1218 wfctx.clearunknown()
1218 wfctx.clearunknown()
1219 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1219 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1220 size = wfctx.write(
1220 size = wfctx.write(
1221 fctx(f).data(),
1221 fctx(f).data(),
1222 flags,
1222 flags,
1223 backgroundclose=True,
1223 backgroundclose=True,
1224 atomictemp=atomictemp,
1224 atomictemp=atomictemp,
1225 )
1225 )
1226 if wantfiledata:
1226 if wantfiledata:
1227 s = wfctx.lstat()
1227 s = wfctx.lstat()
1228 mode = s.st_mode
1228 mode = s.st_mode
1229 mtime = s[stat.ST_MTIME]
1229 mtime = s[stat.ST_MTIME]
1230 filedata[f] = (mode, size, mtime) # for dirstate.normal
1230 filedata[f] = (mode, size, mtime) # for dirstate.normal
1231 if i == 100:
1231 if i == 100:
1232 yield False, (i, f)
1232 yield False, (i, f)
1233 i = 0
1233 i = 0
1234 i += 1
1234 i += 1
1235 if i > 0:
1235 if i > 0:
1236 yield False, (i, f)
1236 yield False, (i, f)
1237 yield True, filedata
1237 yield True, filedata
1238
1238
1239
1239
1240 def _prefetchfiles(repo, ctx, actions):
1240 def _prefetchfiles(repo, ctx, actions):
1241 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1241 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1242 of merge actions. ``ctx`` is the context being merged in."""
1242 of merge actions. ``ctx`` is the context being merged in."""
1243
1243
1244 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1244 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1245 # don't touch the context to be merged in. 'cd' is skipped, because
1245 # don't touch the context to be merged in. 'cd' is skipped, because
1246 # changed/deleted never resolves to something from the remote side.
1246 # changed/deleted never resolves to something from the remote side.
1247 oplist = [
1247 oplist = [
1248 actions[a]
1248 actions[a]
1249 for a in (
1249 for a in (
1250 mergestatemod.ACTION_GET,
1250 mergestatemod.ACTION_GET,
1251 mergestatemod.ACTION_DELETED_CHANGED,
1251 mergestatemod.ACTION_DELETED_CHANGED,
1252 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1252 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1253 mergestatemod.ACTION_MERGE,
1253 mergestatemod.ACTION_MERGE,
1254 )
1254 )
1255 ]
1255 ]
1256 prefetch = scmutil.prefetchfiles
1256 prefetch = scmutil.prefetchfiles
1257 matchfiles = scmutil.matchfiles
1257 matchfiles = scmutil.matchfiles
1258 prefetch(
1258 prefetch(
1259 repo,
1259 repo,
1260 [
1260 [
1261 (
1261 (
1262 ctx.rev(),
1262 ctx.rev(),
1263 matchfiles(
1263 matchfiles(
1264 repo, [f for sublist in oplist for f, args, msg in sublist]
1264 repo, [f for sublist in oplist for f, args, msg in sublist]
1265 ),
1265 ),
1266 )
1266 )
1267 ],
1267 ],
1268 )
1268 )
1269
1269
1270
1270
1271 @attr.s(frozen=True)
1271 @attr.s(frozen=True)
1272 class updateresult(object):
1272 class updateresult(object):
1273 updatedcount = attr.ib()
1273 updatedcount = attr.ib()
1274 mergedcount = attr.ib()
1274 mergedcount = attr.ib()
1275 removedcount = attr.ib()
1275 removedcount = attr.ib()
1276 unresolvedcount = attr.ib()
1276 unresolvedcount = attr.ib()
1277
1277
1278 def isempty(self):
1278 def isempty(self):
1279 return not (
1279 return not (
1280 self.updatedcount
1280 self.updatedcount
1281 or self.mergedcount
1281 or self.mergedcount
1282 or self.removedcount
1282 or self.removedcount
1283 or self.unresolvedcount
1283 or self.unresolvedcount
1284 )
1284 )
1285
1285
1286
1286
1287 def emptyactions():
1287 def emptyactions():
1288 """create an actions dict, to be populated and passed to applyupdates()"""
1288 """create an actions dict, to be populated and passed to applyupdates()"""
1289 return {
1289 return {
1290 m: []
1290 m: []
1291 for m in (
1291 for m in (
1292 mergestatemod.ACTION_ADD,
1292 mergestatemod.ACTION_ADD,
1293 mergestatemod.ACTION_ADD_MODIFIED,
1293 mergestatemod.ACTION_ADD_MODIFIED,
1294 mergestatemod.ACTION_FORGET,
1294 mergestatemod.ACTION_FORGET,
1295 mergestatemod.ACTION_GET,
1295 mergestatemod.ACTION_GET,
1296 mergestatemod.ACTION_CHANGED_DELETED,
1296 mergestatemod.ACTION_CHANGED_DELETED,
1297 mergestatemod.ACTION_DELETED_CHANGED,
1297 mergestatemod.ACTION_DELETED_CHANGED,
1298 mergestatemod.ACTION_REMOVE,
1298 mergestatemod.ACTION_REMOVE,
1299 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1299 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1300 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1300 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1301 mergestatemod.ACTION_MERGE,
1301 mergestatemod.ACTION_MERGE,
1302 mergestatemod.ACTION_EXEC,
1302 mergestatemod.ACTION_EXEC,
1303 mergestatemod.ACTION_KEEP,
1303 mergestatemod.ACTION_KEEP,
1304 mergestatemod.ACTION_PATH_CONFLICT,
1304 mergestatemod.ACTION_PATH_CONFLICT,
1305 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1305 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1306 )
1306 )
1307 }
1307 }
1308
1308
1309
1309
1310 def applyupdates(
1310 def applyupdates(
1311 repo,
1311 repo,
1312 actions,
1312 actions,
1313 wctx,
1313 wctx,
1314 mctx,
1314 mctx,
1315 overwrite,
1315 overwrite,
1316 wantfiledata,
1316 wantfiledata,
1317 labels=None,
1317 labels=None,
1318 commitinfo=None,
1318 commitinfo=None,
1319 ):
1319 ):
1320 """apply the merge action list to the working directory
1320 """apply the merge action list to the working directory
1321
1321
1322 wctx is the working copy context
1322 wctx is the working copy context
1323 mctx is the context to be merged into the working copy
1323 mctx is the context to be merged into the working copy
1324 commitinfo is a mapping of information which needs to be stored somewhere
1324 commitinfo is a mapping of information which needs to be stored somewhere
1325 (probably mergestate) so that it can be used at commit time.
1325 (probably mergestate) so that it can be used at commit time.
1326
1326
1327 Return a tuple of (counts, filedata), where counts is a tuple
1327 Return a tuple of (counts, filedata), where counts is a tuple
1328 (updated, merged, removed, unresolved) that describes how many
1328 (updated, merged, removed, unresolved) that describes how many
1329 files were affected by the update, and filedata is as described in
1329 files were affected by the update, and filedata is as described in
1330 batchget.
1330 batchget.
1331 """
1331 """
1332
1332
1333 _prefetchfiles(repo, mctx, actions)
1333 _prefetchfiles(repo, mctx, actions)
1334
1334
1335 updated, merged, removed = 0, 0, 0
1335 updated, merged, removed = 0, 0, 0
1336 ms = mergestatemod.mergestate.clean(
1336 ms = mergestatemod.mergestate.clean(
1337 repo, wctx.p1().node(), mctx.node(), labels
1337 repo, wctx.p1().node(), mctx.node(), labels
1338 )
1338 )
1339
1339
1340 if commitinfo is None:
1340 if commitinfo is None:
1341 commitinfo = {}
1341 commitinfo = {}
1342
1342
1343 for f, op in pycompat.iteritems(commitinfo):
1343 for f, op in pycompat.iteritems(commitinfo):
1344 # the other side of filenode was choosen while merging, store this in
1344 # the other side of filenode was choosen while merging, store this in
1345 # mergestate so that it can be reused on commit
1345 # mergestate so that it can be reused on commit
1346 if op == b'other':
1346 if op == b'other':
1347 ms.addmergedother(f)
1347 ms.addmergedother(f)
1348
1348
1349 moves = []
1349 moves = []
1350 for m, l in actions.items():
1350 for m, l in actions.items():
1351 l.sort()
1351 l.sort()
1352
1352
1353 # 'cd' and 'dc' actions are treated like other merge conflicts
1353 # 'cd' and 'dc' actions are treated like other merge conflicts
1354 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1354 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1355 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1355 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1356 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1356 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1357 for f, args, msg in mergeactions:
1357 for f, args, msg in mergeactions:
1358 f1, f2, fa, move, anc = args
1358 f1, f2, fa, move, anc = args
1359 if f == b'.hgsubstate': # merged internally
1359 if f == b'.hgsubstate': # merged internally
1360 continue
1360 continue
1361 if f1 is None:
1361 if f1 is None:
1362 fcl = filemerge.absentfilectx(wctx, fa)
1362 fcl = filemerge.absentfilectx(wctx, fa)
1363 else:
1363 else:
1364 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1364 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1365 fcl = wctx[f1]
1365 fcl = wctx[f1]
1366 if f2 is None:
1366 if f2 is None:
1367 fco = filemerge.absentfilectx(mctx, fa)
1367 fco = filemerge.absentfilectx(mctx, fa)
1368 else:
1368 else:
1369 fco = mctx[f2]
1369 fco = mctx[f2]
1370 actx = repo[anc]
1370 actx = repo[anc]
1371 if fa in actx:
1371 if fa in actx:
1372 fca = actx[fa]
1372 fca = actx[fa]
1373 else:
1373 else:
1374 # TODO: move to absentfilectx
1374 # TODO: move to absentfilectx
1375 fca = repo.filectx(f1, fileid=nullrev)
1375 fca = repo.filectx(f1, fileid=nullrev)
1376 ms.add(fcl, fco, fca, f)
1376 ms.add(fcl, fco, fca, f)
1377 if f1 != f and move:
1377 if f1 != f and move:
1378 moves.append(f1)
1378 moves.append(f1)
1379
1379
1380 # remove renamed files after safely stored
1380 # remove renamed files after safely stored
1381 for f in moves:
1381 for f in moves:
1382 if wctx[f].lexists():
1382 if wctx[f].lexists():
1383 repo.ui.debug(b"removing %s\n" % f)
1383 repo.ui.debug(b"removing %s\n" % f)
1384 wctx[f].audit()
1384 wctx[f].audit()
1385 wctx[f].remove()
1385 wctx[f].remove()
1386
1386
1387 numupdates = sum(
1387 numupdates = sum(
1388 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1388 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1389 )
1389 )
1390 progress = repo.ui.makeprogress(
1390 progress = repo.ui.makeprogress(
1391 _(b'updating'), unit=_(b'files'), total=numupdates
1391 _(b'updating'), unit=_(b'files'), total=numupdates
1392 )
1392 )
1393
1393
1394 if [
1394 if [
1395 a
1395 a
1396 for a in actions[mergestatemod.ACTION_REMOVE]
1396 for a in actions[mergestatemod.ACTION_REMOVE]
1397 if a[0] == b'.hgsubstate'
1397 if a[0] == b'.hgsubstate'
1398 ]:
1398 ]:
1399 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1399 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1400
1400
1401 # record path conflicts
1401 # record path conflicts
1402 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1402 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1403 f1, fo = args
1403 f1, fo = args
1404 s = repo.ui.status
1404 s = repo.ui.status
1405 s(
1405 s(
1406 _(
1406 _(
1407 b"%s: path conflict - a file or link has the same name as a "
1407 b"%s: path conflict - a file or link has the same name as a "
1408 b"directory\n"
1408 b"directory\n"
1409 )
1409 )
1410 % f
1410 % f
1411 )
1411 )
1412 if fo == b'l':
1412 if fo == b'l':
1413 s(_(b"the local file has been renamed to %s\n") % f1)
1413 s(_(b"the local file has been renamed to %s\n") % f1)
1414 else:
1414 else:
1415 s(_(b"the remote file has been renamed to %s\n") % f1)
1415 s(_(b"the remote file has been renamed to %s\n") % f1)
1416 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1416 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1417 ms.addpathconflict(f, f1, fo)
1417 ms.addpathconflict(f, f1, fo)
1418 progress.increment(item=f)
1418 progress.increment(item=f)
1419
1419
1420 # When merging in-memory, we can't support worker processes, so set the
1420 # When merging in-memory, we can't support worker processes, so set the
1421 # per-item cost at 0 in that case.
1421 # per-item cost at 0 in that case.
1422 cost = 0 if wctx.isinmemory() else 0.001
1422 cost = 0 if wctx.isinmemory() else 0.001
1423
1423
1424 # remove in parallel (must come before resolving path conflicts and getting)
1424 # remove in parallel (must come before resolving path conflicts and getting)
1425 prog = worker.worker(
1425 prog = worker.worker(
1426 repo.ui,
1426 repo.ui,
1427 cost,
1427 cost,
1428 batchremove,
1428 batchremove,
1429 (repo, wctx),
1429 (repo, wctx),
1430 actions[mergestatemod.ACTION_REMOVE],
1430 actions[mergestatemod.ACTION_REMOVE],
1431 )
1431 )
1432 for i, item in prog:
1432 for i, item in prog:
1433 progress.increment(step=i, item=item)
1433 progress.increment(step=i, item=item)
1434 removed = len(actions[mergestatemod.ACTION_REMOVE])
1434 removed = len(actions[mergestatemod.ACTION_REMOVE])
1435
1435
1436 # resolve path conflicts (must come before getting)
1436 # resolve path conflicts (must come before getting)
1437 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1437 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1438 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1438 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1439 (f0, origf0) = args
1439 (f0, origf0) = args
1440 if wctx[f0].lexists():
1440 if wctx[f0].lexists():
1441 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1441 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1442 wctx[f].audit()
1442 wctx[f].audit()
1443 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1443 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1444 wctx[f0].remove()
1444 wctx[f0].remove()
1445 progress.increment(item=f)
1445 progress.increment(item=f)
1446
1446
1447 # get in parallel.
1447 # get in parallel.
1448 threadsafe = repo.ui.configbool(
1448 threadsafe = repo.ui.configbool(
1449 b'experimental', b'worker.wdir-get-thread-safe'
1449 b'experimental', b'worker.wdir-get-thread-safe'
1450 )
1450 )
1451 prog = worker.worker(
1451 prog = worker.worker(
1452 repo.ui,
1452 repo.ui,
1453 cost,
1453 cost,
1454 batchget,
1454 batchget,
1455 (repo, mctx, wctx, wantfiledata),
1455 (repo, mctx, wctx, wantfiledata),
1456 actions[mergestatemod.ACTION_GET],
1456 actions[mergestatemod.ACTION_GET],
1457 threadsafe=threadsafe,
1457 threadsafe=threadsafe,
1458 hasretval=True,
1458 hasretval=True,
1459 )
1459 )
1460 getfiledata = {}
1460 getfiledata = {}
1461 for final, res in prog:
1461 for final, res in prog:
1462 if final:
1462 if final:
1463 getfiledata = res
1463 getfiledata = res
1464 else:
1464 else:
1465 i, item = res
1465 i, item = res
1466 progress.increment(step=i, item=item)
1466 progress.increment(step=i, item=item)
1467 updated = len(actions[mergestatemod.ACTION_GET])
1467 updated = len(actions[mergestatemod.ACTION_GET])
1468
1468
1469 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1469 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1470 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1470 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1471
1471
1472 # forget (manifest only, just log it) (must come first)
1472 # forget (manifest only, just log it) (must come first)
1473 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1473 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1474 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1474 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1475 progress.increment(item=f)
1475 progress.increment(item=f)
1476
1476
1477 # re-add (manifest only, just log it)
1477 # re-add (manifest only, just log it)
1478 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1478 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1479 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1479 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1480 progress.increment(item=f)
1480 progress.increment(item=f)
1481
1481
1482 # re-add/mark as modified (manifest only, just log it)
1482 # re-add/mark as modified (manifest only, just log it)
1483 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1483 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1484 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1484 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1485 progress.increment(item=f)
1485 progress.increment(item=f)
1486
1486
1487 # keep (noop, just log it)
1487 # keep (noop, just log it)
1488 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
1488 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
1489 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1489 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1490 # no progress
1490 # no progress
1491
1491
1492 # directory rename, move local
1492 # directory rename, move local
1493 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1493 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1494 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1494 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1495 progress.increment(item=f)
1495 progress.increment(item=f)
1496 f0, flags = args
1496 f0, flags = args
1497 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1497 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1498 wctx[f].audit()
1498 wctx[f].audit()
1499 wctx[f].write(wctx.filectx(f0).data(), flags)
1499 wctx[f].write(wctx.filectx(f0).data(), flags)
1500 wctx[f0].remove()
1500 wctx[f0].remove()
1501 updated += 1
1501 updated += 1
1502
1502
1503 # local directory rename, get
1503 # local directory rename, get
1504 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1504 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1505 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1505 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1506 progress.increment(item=f)
1506 progress.increment(item=f)
1507 f0, flags = args
1507 f0, flags = args
1508 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1508 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1509 wctx[f].write(mctx.filectx(f0).data(), flags)
1509 wctx[f].write(mctx.filectx(f0).data(), flags)
1510 updated += 1
1510 updated += 1
1511
1511
1512 # exec
1512 # exec
1513 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
1513 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
1514 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1514 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1515 progress.increment(item=f)
1515 progress.increment(item=f)
1516 (flags,) = args
1516 (flags,) = args
1517 wctx[f].audit()
1517 wctx[f].audit()
1518 wctx[f].setflags(b'l' in flags, b'x' in flags)
1518 wctx[f].setflags(b'l' in flags, b'x' in flags)
1519 updated += 1
1519 updated += 1
1520
1520
1521 # the ordering is important here -- ms.mergedriver will raise if the merge
1521 # the ordering is important here -- ms.mergedriver will raise if the merge
1522 # driver has changed, and we want to be able to bypass it when overwrite is
1522 # driver has changed, and we want to be able to bypass it when overwrite is
1523 # True
1523 # True
1524 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1524 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1525
1525
1526 if usemergedriver:
1526 if usemergedriver:
1527 if wctx.isinmemory():
1527 if wctx.isinmemory():
1528 raise error.InMemoryMergeConflictsError(
1528 raise error.InMemoryMergeConflictsError(
1529 b"in-memory merge does not support mergedriver"
1529 b"in-memory merge does not support mergedriver"
1530 )
1530 )
1531 ms.commit()
1531 ms.commit()
1532 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1532 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1533 # the driver might leave some files unresolved
1533 # the driver might leave some files unresolved
1534 unresolvedf = set(ms.unresolved())
1534 unresolvedf = set(ms.unresolved())
1535 if not proceed:
1535 if not proceed:
1536 # XXX setting unresolved to at least 1 is a hack to make sure we
1536 # XXX setting unresolved to at least 1 is a hack to make sure we
1537 # error out
1537 # error out
1538 return updateresult(
1538 return updateresult(
1539 updated, merged, removed, max(len(unresolvedf), 1)
1539 updated, merged, removed, max(len(unresolvedf), 1)
1540 )
1540 )
1541 newactions = []
1541 newactions = []
1542 for f, args, msg in mergeactions:
1542 for f, args, msg in mergeactions:
1543 if f in unresolvedf:
1543 if f in unresolvedf:
1544 newactions.append((f, args, msg))
1544 newactions.append((f, args, msg))
1545 mergeactions = newactions
1545 mergeactions = newactions
1546
1546
1547 try:
1547 try:
1548 # premerge
1548 # premerge
1549 tocomplete = []
1549 tocomplete = []
1550 for f, args, msg in mergeactions:
1550 for f, args, msg in mergeactions:
1551 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1551 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1552 progress.increment(item=f)
1552 progress.increment(item=f)
1553 if f == b'.hgsubstate': # subrepo states need updating
1553 if f == b'.hgsubstate': # subrepo states need updating
1554 subrepoutil.submerge(
1554 subrepoutil.submerge(
1555 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1555 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1556 )
1556 )
1557 continue
1557 continue
1558 wctx[f].audit()
1558 wctx[f].audit()
1559 complete, r = ms.preresolve(f, wctx)
1559 complete, r = ms.preresolve(f, wctx)
1560 if not complete:
1560 if not complete:
1561 numupdates += 1
1561 numupdates += 1
1562 tocomplete.append((f, args, msg))
1562 tocomplete.append((f, args, msg))
1563
1563
1564 # merge
1564 # merge
1565 for f, args, msg in tocomplete:
1565 for f, args, msg in tocomplete:
1566 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1566 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1567 progress.increment(item=f, total=numupdates)
1567 progress.increment(item=f, total=numupdates)
1568 ms.resolve(f, wctx)
1568 ms.resolve(f, wctx)
1569
1569
1570 finally:
1570 finally:
1571 ms.commit()
1571 ms.commit()
1572
1572
1573 unresolved = ms.unresolvedcount()
1573 unresolved = ms.unresolvedcount()
1574
1574
1575 if (
1575 if (
1576 usemergedriver
1576 usemergedriver
1577 and not unresolved
1577 and not unresolved
1578 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1578 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1579 ):
1579 ):
1580 if not driverconclude(repo, ms, wctx, labels=labels):
1580 if not driverconclude(repo, ms, wctx, labels=labels):
1581 # XXX setting unresolved to at least 1 is a hack to make sure we
1581 # XXX setting unresolved to at least 1 is a hack to make sure we
1582 # error out
1582 # error out
1583 unresolved = max(unresolved, 1)
1583 unresolved = max(unresolved, 1)
1584
1584
1585 ms.commit()
1585 ms.commit()
1586
1586
1587 msupdated, msmerged, msremoved = ms.counts()
1587 msupdated, msmerged, msremoved = ms.counts()
1588 updated += msupdated
1588 updated += msupdated
1589 merged += msmerged
1589 merged += msmerged
1590 removed += msremoved
1590 removed += msremoved
1591
1591
1592 extraactions = ms.actions()
1592 extraactions = ms.actions()
1593 if extraactions:
1593 if extraactions:
1594 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
1594 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
1595 for k, acts in pycompat.iteritems(extraactions):
1595 for k, acts in pycompat.iteritems(extraactions):
1596 actions[k].extend(acts)
1596 actions[k].extend(acts)
1597 if k == mergestatemod.ACTION_GET and wantfiledata:
1597 if k == mergestatemod.ACTION_GET and wantfiledata:
1598 # no filedata until mergestate is updated to provide it
1598 # no filedata until mergestate is updated to provide it
1599 for a in acts:
1599 for a in acts:
1600 getfiledata[a[0]] = None
1600 getfiledata[a[0]] = None
1601 # Remove these files from actions[ACTION_MERGE] as well. This is
1601 # Remove these files from actions[ACTION_MERGE] as well. This is
1602 # important because in recordupdates, files in actions[ACTION_MERGE]
1602 # important because in recordupdates, files in actions[ACTION_MERGE]
1603 # are processed after files in other actions, and the merge driver
1603 # are processed after files in other actions, and the merge driver
1604 # might add files to those actions via extraactions above. This can
1604 # might add files to those actions via extraactions above. This can
1605 # lead to a file being recorded twice, with poor results. This is
1605 # lead to a file being recorded twice, with poor results. This is
1606 # especially problematic for actions[ACTION_REMOVE] (currently only
1606 # especially problematic for actions[ACTION_REMOVE] (currently only
1607 # possible with the merge driver in the initial merge process;
1607 # possible with the merge driver in the initial merge process;
1608 # interrupted merges don't go through this flow).
1608 # interrupted merges don't go through this flow).
1609 #
1609 #
1610 # The real fix here is to have indexes by both file and action so
1610 # The real fix here is to have indexes by both file and action so
1611 # that when the action for a file is changed it is automatically
1611 # that when the action for a file is changed it is automatically
1612 # reflected in the other action lists. But that involves a more
1612 # reflected in the other action lists. But that involves a more
1613 # complex data structure, so this will do for now.
1613 # complex data structure, so this will do for now.
1614 #
1614 #
1615 # We don't need to do the same operation for 'dc' and 'cd' because
1615 # We don't need to do the same operation for 'dc' and 'cd' because
1616 # those lists aren't consulted again.
1616 # those lists aren't consulted again.
1617 mfiles.difference_update(a[0] for a in acts)
1617 mfiles.difference_update(a[0] for a in acts)
1618
1618
1619 actions[mergestatemod.ACTION_MERGE] = [
1619 actions[mergestatemod.ACTION_MERGE] = [
1620 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
1620 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
1621 ]
1621 ]
1622
1622
1623 progress.complete()
1623 progress.complete()
1624 assert len(getfiledata) == (
1624 assert len(getfiledata) == (
1625 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1625 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1626 )
1626 )
1627 return updateresult(updated, merged, removed, unresolved), getfiledata
1627 return updateresult(updated, merged, removed, unresolved), getfiledata
1628
1628
1629
1629
1630 def _advertisefsmonitor(repo, num_gets, p1node):
1630 def _advertisefsmonitor(repo, num_gets, p1node):
1631 # Advertise fsmonitor when its presence could be useful.
1631 # Advertise fsmonitor when its presence could be useful.
1632 #
1632 #
1633 # We only advertise when performing an update from an empty working
1633 # We only advertise when performing an update from an empty working
1634 # directory. This typically only occurs during initial clone.
1634 # directory. This typically only occurs during initial clone.
1635 #
1635 #
1636 # We give users a mechanism to disable the warning in case it is
1636 # We give users a mechanism to disable the warning in case it is
1637 # annoying.
1637 # annoying.
1638 #
1638 #
1639 # We only allow on Linux and MacOS because that's where fsmonitor is
1639 # We only allow on Linux and MacOS because that's where fsmonitor is
1640 # considered stable.
1640 # considered stable.
1641 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1641 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1642 fsmonitorthreshold = repo.ui.configint(
1642 fsmonitorthreshold = repo.ui.configint(
1643 b'fsmonitor', b'warn_update_file_count'
1643 b'fsmonitor', b'warn_update_file_count'
1644 )
1644 )
1645 try:
1645 try:
1646 # avoid cycle: extensions -> cmdutil -> merge
1646 # avoid cycle: extensions -> cmdutil -> merge
1647 from . import extensions
1647 from . import extensions
1648
1648
1649 extensions.find(b'fsmonitor')
1649 extensions.find(b'fsmonitor')
1650 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1650 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1651 # We intentionally don't look at whether fsmonitor has disabled
1651 # We intentionally don't look at whether fsmonitor has disabled
1652 # itself because a) fsmonitor may have already printed a warning
1652 # itself because a) fsmonitor may have already printed a warning
1653 # b) we only care about the config state here.
1653 # b) we only care about the config state here.
1654 except KeyError:
1654 except KeyError:
1655 fsmonitorenabled = False
1655 fsmonitorenabled = False
1656
1656
1657 if (
1657 if (
1658 fsmonitorwarning
1658 fsmonitorwarning
1659 and not fsmonitorenabled
1659 and not fsmonitorenabled
1660 and p1node == nullid
1660 and p1node == nullid
1661 and num_gets >= fsmonitorthreshold
1661 and num_gets >= fsmonitorthreshold
1662 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1662 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1663 ):
1663 ):
1664 repo.ui.warn(
1664 repo.ui.warn(
1665 _(
1665 _(
1666 b'(warning: large working directory being used without '
1666 b'(warning: large working directory being used without '
1667 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1667 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1668 b'see "hg help -e fsmonitor")\n'
1668 b'see "hg help -e fsmonitor")\n'
1669 )
1669 )
1670 )
1670 )
1671
1671
1672
1672
1673 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1673 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1674 UPDATECHECK_NONE = b'none'
1674 UPDATECHECK_NONE = b'none'
1675 UPDATECHECK_LINEAR = b'linear'
1675 UPDATECHECK_LINEAR = b'linear'
1676 UPDATECHECK_NO_CONFLICT = b'noconflict'
1676 UPDATECHECK_NO_CONFLICT = b'noconflict'
1677
1677
1678
1678
1679 def update(
1679 def update(
1680 repo,
1680 repo,
1681 node,
1681 node,
1682 branchmerge,
1682 branchmerge,
1683 force,
1683 force,
1684 ancestor=None,
1684 ancestor=None,
1685 mergeancestor=False,
1685 mergeancestor=False,
1686 labels=None,
1686 labels=None,
1687 matcher=None,
1687 matcher=None,
1688 mergeforce=False,
1688 mergeforce=False,
1689 updatedirstate=True,
1689 updatedirstate=True,
1690 updatecheck=None,
1690 updatecheck=None,
1691 wc=None,
1691 wc=None,
1692 ):
1692 ):
1693 """
1693 """
1694 Perform a merge between the working directory and the given node
1694 Perform a merge between the working directory and the given node
1695
1695
1696 node = the node to update to
1696 node = the node to update to
1697 branchmerge = whether to merge between branches
1697 branchmerge = whether to merge between branches
1698 force = whether to force branch merging or file overwriting
1698 force = whether to force branch merging or file overwriting
1699 matcher = a matcher to filter file lists (dirstate not updated)
1699 matcher = a matcher to filter file lists (dirstate not updated)
1700 mergeancestor = whether it is merging with an ancestor. If true,
1700 mergeancestor = whether it is merging with an ancestor. If true,
1701 we should accept the incoming changes for any prompts that occur.
1701 we should accept the incoming changes for any prompts that occur.
1702 If false, merging with an ancestor (fast-forward) is only allowed
1702 If false, merging with an ancestor (fast-forward) is only allowed
1703 between different named branches. This flag is used by rebase extension
1703 between different named branches. This flag is used by rebase extension
1704 as a temporary fix and should be avoided in general.
1704 as a temporary fix and should be avoided in general.
1705 labels = labels to use for base, local and other
1705 labels = labels to use for base, local and other
1706 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1706 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1707 this is True, then 'force' should be True as well.
1707 this is True, then 'force' should be True as well.
1708
1708
1709 The table below shows all the behaviors of the update command given the
1709 The table below shows all the behaviors of the update command given the
1710 -c/--check and -C/--clean or no options, whether the working directory is
1710 -c/--check and -C/--clean or no options, whether the working directory is
1711 dirty, whether a revision is specified, and the relationship of the parent
1711 dirty, whether a revision is specified, and the relationship of the parent
1712 rev to the target rev (linear or not). Match from top first. The -n
1712 rev to the target rev (linear or not). Match from top first. The -n
1713 option doesn't exist on the command line, but represents the
1713 option doesn't exist on the command line, but represents the
1714 experimental.updatecheck=noconflict option.
1714 experimental.updatecheck=noconflict option.
1715
1715
1716 This logic is tested by test-update-branches.t.
1716 This logic is tested by test-update-branches.t.
1717
1717
1718 -c -C -n -m dirty rev linear | result
1718 -c -C -n -m dirty rev linear | result
1719 y y * * * * * | (1)
1719 y y * * * * * | (1)
1720 y * y * * * * | (1)
1720 y * y * * * * | (1)
1721 y * * y * * * | (1)
1721 y * * y * * * | (1)
1722 * y y * * * * | (1)
1722 * y y * * * * | (1)
1723 * y * y * * * | (1)
1723 * y * y * * * | (1)
1724 * * y y * * * | (1)
1724 * * y y * * * | (1)
1725 * * * * * n n | x
1725 * * * * * n n | x
1726 * * * * n * * | ok
1726 * * * * n * * | ok
1727 n n n n y * y | merge
1727 n n n n y * y | merge
1728 n n n n y y n | (2)
1728 n n n n y y n | (2)
1729 n n n y y * * | merge
1729 n n n y y * * | merge
1730 n n y n y * * | merge if no conflict
1730 n n y n y * * | merge if no conflict
1731 n y n n y * * | discard
1731 n y n n y * * | discard
1732 y n n n y * * | (3)
1732 y n n n y * * | (3)
1733
1733
1734 x = can't happen
1734 x = can't happen
1735 * = don't-care
1735 * = don't-care
1736 1 = incompatible options (checked in commands.py)
1736 1 = incompatible options (checked in commands.py)
1737 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1737 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1738 3 = abort: uncommitted changes (checked in commands.py)
1738 3 = abort: uncommitted changes (checked in commands.py)
1739
1739
1740 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1740 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1741 to repo[None] if None is passed.
1741 to repo[None] if None is passed.
1742
1742
1743 Return the same tuple as applyupdates().
1743 Return the same tuple as applyupdates().
1744 """
1744 """
1745 # Avoid cycle.
1745 # Avoid cycle.
1746 from . import sparse
1746 from . import sparse
1747
1747
1748 # This function used to find the default destination if node was None, but
1748 # This function used to find the default destination if node was None, but
1749 # that's now in destutil.py.
1749 # that's now in destutil.py.
1750 assert node is not None
1750 assert node is not None
1751 if not branchmerge and not force:
1751 if not branchmerge and not force:
1752 # TODO: remove the default once all callers that pass branchmerge=False
1752 # TODO: remove the default once all callers that pass branchmerge=False
1753 # and force=False pass a value for updatecheck. We may want to allow
1753 # and force=False pass a value for updatecheck. We may want to allow
1754 # updatecheck='abort' to better suppport some of these callers.
1754 # updatecheck='abort' to better suppport some of these callers.
1755 if updatecheck is None:
1755 if updatecheck is None:
1756 updatecheck = UPDATECHECK_LINEAR
1756 updatecheck = UPDATECHECK_LINEAR
1757 if updatecheck not in (
1757 if updatecheck not in (
1758 UPDATECHECK_NONE,
1758 UPDATECHECK_NONE,
1759 UPDATECHECK_LINEAR,
1759 UPDATECHECK_LINEAR,
1760 UPDATECHECK_NO_CONFLICT,
1760 UPDATECHECK_NO_CONFLICT,
1761 ):
1761 ):
1762 raise ValueError(
1762 raise ValueError(
1763 r'Invalid updatecheck %r (can accept %r)'
1763 r'Invalid updatecheck %r (can accept %r)'
1764 % (
1764 % (
1765 updatecheck,
1765 updatecheck,
1766 (
1766 (
1767 UPDATECHECK_NONE,
1767 UPDATECHECK_NONE,
1768 UPDATECHECK_LINEAR,
1768 UPDATECHECK_LINEAR,
1769 UPDATECHECK_NO_CONFLICT,
1769 UPDATECHECK_NO_CONFLICT,
1770 ),
1770 ),
1771 )
1771 )
1772 )
1772 )
1773 if wc is not None and wc.isinmemory():
1773 if wc is not None and wc.isinmemory():
1774 maybe_wlock = util.nullcontextmanager()
1774 maybe_wlock = util.nullcontextmanager()
1775 else:
1775 else:
1776 maybe_wlock = repo.wlock()
1776 maybe_wlock = repo.wlock()
1777 with maybe_wlock:
1777 with maybe_wlock:
1778 if wc is None:
1778 if wc is None:
1779 wc = repo[None]
1779 wc = repo[None]
1780 pl = wc.parents()
1780 pl = wc.parents()
1781 p1 = pl[0]
1781 p1 = pl[0]
1782 p2 = repo[node]
1782 p2 = repo[node]
1783 if ancestor is not None:
1783 if ancestor is not None:
1784 pas = [repo[ancestor]]
1784 pas = [repo[ancestor]]
1785 else:
1785 else:
1786 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1786 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1787 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1787 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1788 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1788 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1789 else:
1789 else:
1790 pas = [p1.ancestor(p2, warn=branchmerge)]
1790 pas = [p1.ancestor(p2, warn=branchmerge)]
1791
1791
1792 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1792 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1793
1793
1794 overwrite = force and not branchmerge
1794 overwrite = force and not branchmerge
1795 ### check phase
1795 ### check phase
1796 if not overwrite:
1796 if not overwrite:
1797 if len(pl) > 1:
1797 if len(pl) > 1:
1798 raise error.Abort(_(b"outstanding uncommitted merge"))
1798 raise error.Abort(_(b"outstanding uncommitted merge"))
1799 ms = mergestatemod.mergestate.read(repo)
1799 ms = mergestatemod.mergestate.read(repo)
1800 if list(ms.unresolved()):
1800 if list(ms.unresolved()):
1801 raise error.Abort(
1801 raise error.Abort(
1802 _(b"outstanding merge conflicts"),
1802 _(b"outstanding merge conflicts"),
1803 hint=_(b"use 'hg resolve' to resolve"),
1803 hint=_(b"use 'hg resolve' to resolve"),
1804 )
1804 )
1805 if branchmerge:
1805 if branchmerge:
1806 if pas == [p2]:
1806 if pas == [p2]:
1807 raise error.Abort(
1807 raise error.Abort(
1808 _(
1808 _(
1809 b"merging with a working directory ancestor"
1809 b"merging with a working directory ancestor"
1810 b" has no effect"
1810 b" has no effect"
1811 )
1811 )
1812 )
1812 )
1813 elif pas == [p1]:
1813 elif pas == [p1]:
1814 if not mergeancestor and wc.branch() == p2.branch():
1814 if not mergeancestor and wc.branch() == p2.branch():
1815 raise error.Abort(
1815 raise error.Abort(
1816 _(b"nothing to merge"),
1816 _(b"nothing to merge"),
1817 hint=_(b"use 'hg update' or check 'hg heads'"),
1817 hint=_(b"use 'hg update' or check 'hg heads'"),
1818 )
1818 )
1819 if not force and (wc.files() or wc.deleted()):
1819 if not force and (wc.files() or wc.deleted()):
1820 raise error.Abort(
1820 raise error.Abort(
1821 _(b"uncommitted changes"),
1821 _(b"uncommitted changes"),
1822 hint=_(b"use 'hg status' to list changes"),
1822 hint=_(b"use 'hg status' to list changes"),
1823 )
1823 )
1824 if not wc.isinmemory():
1824 if not wc.isinmemory():
1825 for s in sorted(wc.substate):
1825 for s in sorted(wc.substate):
1826 wc.sub(s).bailifchanged()
1826 wc.sub(s).bailifchanged()
1827
1827
1828 elif not overwrite:
1828 elif not overwrite:
1829 if p1 == p2: # no-op update
1829 if p1 == p2: # no-op update
1830 # call the hooks and exit early
1830 # call the hooks and exit early
1831 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1831 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1832 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1832 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1833 return updateresult(0, 0, 0, 0)
1833 return updateresult(0, 0, 0, 0)
1834
1834
1835 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1835 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1836 [p1],
1836 [p1],
1837 [p2],
1837 [p2],
1838 ): # nonlinear
1838 ): # nonlinear
1839 dirty = wc.dirty(missing=True)
1839 dirty = wc.dirty(missing=True)
1840 if dirty:
1840 if dirty:
1841 # Branching is a bit strange to ensure we do the minimal
1841 # Branching is a bit strange to ensure we do the minimal
1842 # amount of call to obsutil.foreground.
1842 # amount of call to obsutil.foreground.
1843 foreground = obsutil.foreground(repo, [p1.node()])
1843 foreground = obsutil.foreground(repo, [p1.node()])
1844 # note: the <node> variable contains a random identifier
1844 # note: the <node> variable contains a random identifier
1845 if repo[node].node() in foreground:
1845 if repo[node].node() in foreground:
1846 pass # allow updating to successors
1846 pass # allow updating to successors
1847 else:
1847 else:
1848 msg = _(b"uncommitted changes")
1848 msg = _(b"uncommitted changes")
1849 hint = _(b"commit or update --clean to discard changes")
1849 hint = _(b"commit or update --clean to discard changes")
1850 raise error.UpdateAbort(msg, hint=hint)
1850 raise error.UpdateAbort(msg, hint=hint)
1851 else:
1851 else:
1852 # Allow jumping branches if clean and specific rev given
1852 # Allow jumping branches if clean and specific rev given
1853 pass
1853 pass
1854
1854
1855 if overwrite:
1855 if overwrite:
1856 pas = [wc]
1856 pas = [wc]
1857 elif not branchmerge:
1857 elif not branchmerge:
1858 pas = [p1]
1858 pas = [p1]
1859
1859
1860 # deprecated config: merge.followcopies
1860 # deprecated config: merge.followcopies
1861 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1861 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1862 if overwrite:
1862 if overwrite:
1863 followcopies = False
1863 followcopies = False
1864 elif not pas[0]:
1864 elif not pas[0]:
1865 followcopies = False
1865 followcopies = False
1866 if not branchmerge and not wc.dirty(missing=True):
1866 if not branchmerge and not wc.dirty(missing=True):
1867 followcopies = False
1867 followcopies = False
1868
1868
1869 ### calculate phase
1869 ### calculate phase
1870 mresult = calculateupdates(
1870 mresult = calculateupdates(
1871 repo,
1871 repo,
1872 wc,
1872 wc,
1873 p2,
1873 p2,
1874 pas,
1874 pas,
1875 branchmerge,
1875 branchmerge,
1876 force,
1876 force,
1877 mergeancestor,
1877 mergeancestor,
1878 followcopies,
1878 followcopies,
1879 matcher=matcher,
1879 matcher=matcher,
1880 mergeforce=mergeforce,
1880 mergeforce=mergeforce,
1881 )
1881 )
1882
1882
1883 if updatecheck == UPDATECHECK_NO_CONFLICT:
1883 if updatecheck == UPDATECHECK_NO_CONFLICT:
1884 if mresult.hasconflicts():
1884 if mresult.hasconflicts():
1885 msg = _(b"conflicting changes")
1885 msg = _(b"conflicting changes")
1886 hint = _(b"commit or update --clean to discard changes")
1886 hint = _(b"commit or update --clean to discard changes")
1887 raise error.Abort(msg, hint=hint)
1887 raise error.Abort(msg, hint=hint)
1888
1888
1889 # Prompt and create actions. Most of this is in the resolve phase
1889 # Prompt and create actions. Most of this is in the resolve phase
1890 # already, but we can't handle .hgsubstate in filemerge or
1890 # already, but we can't handle .hgsubstate in filemerge or
1891 # subrepoutil.submerge yet so we have to keep prompting for it.
1891 # subrepoutil.submerge yet so we have to keep prompting for it.
1892 if b'.hgsubstate' in mresult.actions:
1892 if b'.hgsubstate' in mresult.actions:
1893 f = b'.hgsubstate'
1893 f = b'.hgsubstate'
1894 m, args, msg = mresult.actions[f]
1894 m, args, msg = mresult.actions[f]
1895 prompts = filemerge.partextras(labels)
1895 prompts = filemerge.partextras(labels)
1896 prompts[b'f'] = f
1896 prompts[b'f'] = f
1897 if m == mergestatemod.ACTION_CHANGED_DELETED:
1897 if m == mergestatemod.ACTION_CHANGED_DELETED:
1898 if repo.ui.promptchoice(
1898 if repo.ui.promptchoice(
1899 _(
1899 _(
1900 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1900 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1901 b"use (c)hanged version or (d)elete?"
1901 b"use (c)hanged version or (d)elete?"
1902 b"$$ &Changed $$ &Delete"
1902 b"$$ &Changed $$ &Delete"
1903 )
1903 )
1904 % prompts,
1904 % prompts,
1905 0,
1905 0,
1906 ):
1906 ):
1907 mresult.addfile(
1907 mresult.addfile(
1908 f, mergestatemod.ACTION_REMOVE, None, b'prompt delete',
1908 f, mergestatemod.ACTION_REMOVE, None, b'prompt delete',
1909 )
1909 )
1910 elif f in p1:
1910 elif f in p1:
1911 mresult.addfile(
1911 mresult.addfile(
1912 f,
1912 f,
1913 mergestatemod.ACTION_ADD_MODIFIED,
1913 mergestatemod.ACTION_ADD_MODIFIED,
1914 None,
1914 None,
1915 b'prompt keep',
1915 b'prompt keep',
1916 )
1916 )
1917 else:
1917 else:
1918 mresult.addfile(
1918 mresult.addfile(
1919 f, mergestatemod.ACTION_ADD, None, b'prompt keep',
1919 f, mergestatemod.ACTION_ADD, None, b'prompt keep',
1920 )
1920 )
1921 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1921 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1922 f1, f2, fa, move, anc = args
1922 f1, f2, fa, move, anc = args
1923 flags = p2[f2].flags()
1923 flags = p2[f2].flags()
1924 if (
1924 if (
1925 repo.ui.promptchoice(
1925 repo.ui.promptchoice(
1926 _(
1926 _(
1927 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1927 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1928 b"use (c)hanged version or leave (d)eleted?"
1928 b"use (c)hanged version or leave (d)eleted?"
1929 b"$$ &Changed $$ &Deleted"
1929 b"$$ &Changed $$ &Deleted"
1930 )
1930 )
1931 % prompts,
1931 % prompts,
1932 0,
1932 0,
1933 )
1933 )
1934 == 0
1934 == 0
1935 ):
1935 ):
1936 mresult.addfile(
1936 mresult.addfile(
1937 f,
1937 f,
1938 mergestatemod.ACTION_GET,
1938 mergestatemod.ACTION_GET,
1939 (flags, False),
1939 (flags, False),
1940 b'prompt recreating',
1940 b'prompt recreating',
1941 )
1941 )
1942 else:
1942 else:
1943 mresult.removefile(f)
1943 mresult.removefile(f)
1944
1944
1945 # Convert to dictionary-of-lists format
1945 # Convert to dictionary-of-lists format
1946 actions = mresult.actionsdict
1946 actions = mresult.actionsdict
1947
1947
1948 if not util.fscasesensitive(repo.path):
1948 if not util.fscasesensitive(repo.path):
1949 # check collision between files only in p2 for clean update
1949 # check collision between files only in p2 for clean update
1950 if not branchmerge and (
1950 if not branchmerge and (
1951 force or not wc.dirty(missing=True, branch=False)
1951 force or not wc.dirty(missing=True, branch=False)
1952 ):
1952 ):
1953 _checkcollision(repo, p2.manifest(), None)
1953 _checkcollision(repo, p2.manifest(), None)
1954 else:
1954 else:
1955 _checkcollision(repo, wc.manifest(), actions)
1955 _checkcollision(repo, wc.manifest(), actions)
1956
1956
1957 # divergent renames
1957 # divergent renames
1958 for f, fl in sorted(pycompat.iteritems(mresult.diverge)):
1958 for f, fl in sorted(pycompat.iteritems(mresult.diverge)):
1959 repo.ui.warn(
1959 repo.ui.warn(
1960 _(
1960 _(
1961 b"note: possible conflict - %s was renamed "
1961 b"note: possible conflict - %s was renamed "
1962 b"multiple times to:\n"
1962 b"multiple times to:\n"
1963 )
1963 )
1964 % f
1964 % f
1965 )
1965 )
1966 for nf in sorted(fl):
1966 for nf in sorted(fl):
1967 repo.ui.warn(b" %s\n" % nf)
1967 repo.ui.warn(b" %s\n" % nf)
1968
1968
1969 # rename and delete
1969 # rename and delete
1970 for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)):
1970 for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)):
1971 repo.ui.warn(
1971 repo.ui.warn(
1972 _(
1972 _(
1973 b"note: possible conflict - %s was deleted "
1973 b"note: possible conflict - %s was deleted "
1974 b"and renamed to:\n"
1974 b"and renamed to:\n"
1975 )
1975 )
1976 % f
1976 % f
1977 )
1977 )
1978 for nf in sorted(fl):
1978 for nf in sorted(fl):
1979 repo.ui.warn(b" %s\n" % nf)
1979 repo.ui.warn(b" %s\n" % nf)
1980
1980
1981 ### apply phase
1981 ### apply phase
1982 if not branchmerge: # just jump to the new rev
1982 if not branchmerge: # just jump to the new rev
1983 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1983 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1984 # If we're doing a partial update, we need to skip updating
1984 # If we're doing a partial update, we need to skip updating
1985 # the dirstate.
1985 # the dirstate.
1986 always = matcher is None or matcher.always()
1986 always = matcher is None or matcher.always()
1987 updatedirstate = updatedirstate and always and not wc.isinmemory()
1987 updatedirstate = updatedirstate and always and not wc.isinmemory()
1988 if updatedirstate:
1988 if updatedirstate:
1989 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
1989 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
1990 # note that we're in the middle of an update
1990 # note that we're in the middle of an update
1991 repo.vfs.write(b'updatestate', p2.hex())
1991 repo.vfs.write(b'updatestate', p2.hex())
1992
1992
1993 _advertisefsmonitor(
1993 _advertisefsmonitor(
1994 repo, len(actions[mergestatemod.ACTION_GET]), p1.node()
1994 repo, len(actions[mergestatemod.ACTION_GET]), p1.node()
1995 )
1995 )
1996
1996
1997 wantfiledata = updatedirstate and not branchmerge
1997 wantfiledata = updatedirstate and not branchmerge
1998 stats, getfiledata = applyupdates(
1998 stats, getfiledata = applyupdates(
1999 repo,
1999 repo,
2000 actions,
2000 actions,
2001 wc,
2001 wc,
2002 p2,
2002 p2,
2003 overwrite,
2003 overwrite,
2004 wantfiledata,
2004 wantfiledata,
2005 labels=labels,
2005 labels=labels,
2006 commitinfo=mresult.commitinfo,
2006 commitinfo=mresult.commitinfo,
2007 )
2007 )
2008
2008
2009 if updatedirstate:
2009 if updatedirstate:
2010 with repo.dirstate.parentchange():
2010 with repo.dirstate.parentchange():
2011 repo.setparents(fp1, fp2)
2011 repo.setparents(fp1, fp2)
2012 mergestatemod.recordupdates(
2012 mergestatemod.recordupdates(
2013 repo, actions, branchmerge, getfiledata
2013 repo, actions, branchmerge, getfiledata
2014 )
2014 )
2015 # update completed, clear state
2015 # update completed, clear state
2016 util.unlink(repo.vfs.join(b'updatestate'))
2016 util.unlink(repo.vfs.join(b'updatestate'))
2017
2017
2018 if not branchmerge:
2018 if not branchmerge:
2019 repo.dirstate.setbranch(p2.branch())
2019 repo.dirstate.setbranch(p2.branch())
2020
2020
2021 # If we're updating to a location, clean up any stale temporary includes
2021 # If we're updating to a location, clean up any stale temporary includes
2022 # (ex: this happens during hg rebase --abort).
2022 # (ex: this happens during hg rebase --abort).
2023 if not branchmerge:
2023 if not branchmerge:
2024 sparse.prunetemporaryincludes(repo)
2024 sparse.prunetemporaryincludes(repo)
2025
2025
2026 if updatedirstate:
2026 if updatedirstate:
2027 repo.hook(
2027 repo.hook(
2028 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2028 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2029 )
2029 )
2030 return stats
2030 return stats
2031
2031
2032
2032
2033 def merge(ctx, labels=None, force=False, wc=None):
2033 def merge(ctx, labels=None, force=False, wc=None):
2034 """Merge another topological branch into the working copy.
2034 """Merge another topological branch into the working copy.
2035
2035
2036 force = whether the merge was run with 'merge --force' (deprecated)
2036 force = whether the merge was run with 'merge --force' (deprecated)
2037 """
2037 """
2038
2038
2039 return update(
2039 return update(
2040 ctx.repo(),
2040 ctx.repo(),
2041 ctx.rev(),
2041 ctx.rev(),
2042 labels=labels,
2042 labels=labels,
2043 branchmerge=True,
2043 branchmerge=True,
2044 force=force,
2044 force=force,
2045 mergeforce=force,
2045 mergeforce=force,
2046 wc=wc,
2046 wc=wc,
2047 )
2047 )
2048
2048
2049
2049
2050 def clean_update(ctx, wc=None):
2050 def clean_update(ctx, wc=None):
2051 """Do a clean update to the given commit.
2051 """Do a clean update to the given commit.
2052
2052
2053 This involves updating to the commit and discarding any changes in the
2053 This involves updating to the commit and discarding any changes in the
2054 working copy.
2054 working copy.
2055 """
2055 """
2056 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2056 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2057
2057
2058
2058
2059 def revert_to(ctx, matcher=None, wc=None):
2059 def revert_to(ctx, matcher=None, wc=None):
2060 """Revert the working copy to the given commit.
2060 """Revert the working copy to the given commit.
2061
2061
2062 The working copy will keep its current parent(s) but its content will
2062 The working copy will keep its current parent(s) but its content will
2063 be the same as in the given commit.
2063 be the same as in the given commit.
2064 """
2064 """
2065
2065
2066 return update(
2066 return update(
2067 ctx.repo(),
2067 ctx.repo(),
2068 ctx.rev(),
2068 ctx.rev(),
2069 branchmerge=False,
2069 branchmerge=False,
2070 force=True,
2070 force=True,
2071 updatedirstate=False,
2071 updatedirstate=False,
2072 matcher=matcher,
2072 matcher=matcher,
2073 wc=wc,
2073 wc=wc,
2074 )
2074 )
2075
2075
2076
2076
2077 def graft(
2077 def graft(
2078 repo,
2078 repo,
2079 ctx,
2079 ctx,
2080 base=None,
2080 base=None,
2081 labels=None,
2081 labels=None,
2082 keepparent=False,
2082 keepparent=False,
2083 keepconflictparent=False,
2083 keepconflictparent=False,
2084 wctx=None,
2084 wctx=None,
2085 ):
2085 ):
2086 """Do a graft-like merge.
2086 """Do a graft-like merge.
2087
2087
2088 This is a merge where the merge ancestor is chosen such that one
2088 This is a merge where the merge ancestor is chosen such that one
2089 or more changesets are grafted onto the current changeset. In
2089 or more changesets are grafted onto the current changeset. In
2090 addition to the merge, this fixes up the dirstate to include only
2090 addition to the merge, this fixes up the dirstate to include only
2091 a single parent (if keepparent is False) and tries to duplicate any
2091 a single parent (if keepparent is False) and tries to duplicate any
2092 renames/copies appropriately.
2092 renames/copies appropriately.
2093
2093
2094 ctx - changeset to rebase
2094 ctx - changeset to rebase
2095 base - merge base, or ctx.p1() if not specified
2095 base - merge base, or ctx.p1() if not specified
2096 labels - merge labels eg ['local', 'graft']
2096 labels - merge labels eg ['local', 'graft']
2097 keepparent - keep second parent if any
2097 keepparent - keep second parent if any
2098 keepconflictparent - if unresolved, keep parent used for the merge
2098 keepconflictparent - if unresolved, keep parent used for the merge
2099
2099
2100 """
2100 """
2101 # If we're grafting a descendant onto an ancestor, be sure to pass
2101 # If we're grafting a descendant onto an ancestor, be sure to pass
2102 # mergeancestor=True to update. This does two things: 1) allows the merge if
2102 # mergeancestor=True to update. This does two things: 1) allows the merge if
2103 # the destination is the same as the parent of the ctx (so we can use graft
2103 # the destination is the same as the parent of the ctx (so we can use graft
2104 # to copy commits), and 2) informs update that the incoming changes are
2104 # to copy commits), and 2) informs update that the incoming changes are
2105 # newer than the destination so it doesn't prompt about "remote changed foo
2105 # newer than the destination so it doesn't prompt about "remote changed foo
2106 # which local deleted".
2106 # which local deleted".
2107 # We also pass mergeancestor=True when base is the same revision as p1. 2)
2107 # We also pass mergeancestor=True when base is the same revision as p1. 2)
2108 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
2108 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
2109 wctx = wctx or repo[None]
2109 wctx = wctx or repo[None]
2110 pctx = wctx.p1()
2110 pctx = wctx.p1()
2111 base = base or ctx.p1()
2111 base = base or ctx.p1()
2112 mergeancestor = (
2112 mergeancestor = (
2113 repo.changelog.isancestor(pctx.node(), ctx.node())
2113 repo.changelog.isancestor(pctx.node(), ctx.node())
2114 or pctx.rev() == base.rev()
2114 or pctx.rev() == base.rev()
2115 )
2115 )
2116
2116
2117 stats = update(
2117 stats = update(
2118 repo,
2118 repo,
2119 ctx.node(),
2119 ctx.node(),
2120 True,
2120 True,
2121 True,
2121 True,
2122 base.node(),
2122 base.node(),
2123 mergeancestor=mergeancestor,
2123 mergeancestor=mergeancestor,
2124 labels=labels,
2124 labels=labels,
2125 wc=wctx,
2125 wc=wctx,
2126 )
2126 )
2127
2127
2128 if keepconflictparent and stats.unresolvedcount:
2128 if keepconflictparent and stats.unresolvedcount:
2129 pother = ctx.node()
2129 pother = ctx.node()
2130 else:
2130 else:
2131 pother = nullid
2131 pother = nullid
2132 parents = ctx.parents()
2132 parents = ctx.parents()
2133 if keepparent and len(parents) == 2 and base in parents:
2133 if keepparent and len(parents) == 2 and base in parents:
2134 parents.remove(base)
2134 parents.remove(base)
2135 pother = parents[0].node()
2135 pother = parents[0].node()
2136 # Never set both parents equal to each other
2136 # Never set both parents equal to each other
2137 if pother == pctx.node():
2137 if pother == pctx.node():
2138 pother = nullid
2138 pother = nullid
2139
2139
2140 if wctx.isinmemory():
2140 if wctx.isinmemory():
2141 wctx.setparents(pctx.node(), pother)
2141 wctx.setparents(pctx.node(), pother)
2142 # fix up dirstate for copies and renames
2142 # fix up dirstate for copies and renames
2143 copies.graftcopies(wctx, ctx, base)
2143 copies.graftcopies(wctx, ctx, base)
2144 else:
2144 else:
2145 with repo.dirstate.parentchange():
2145 with repo.dirstate.parentchange():
2146 repo.setparents(pctx.node(), pother)
2146 repo.setparents(pctx.node(), pother)
2147 repo.dirstate.write(repo.currenttransaction())
2147 repo.dirstate.write(repo.currenttransaction())
2148 # fix up dirstate for copies and renames
2148 # fix up dirstate for copies and renames
2149 copies.graftcopies(wctx, ctx, base)
2149 copies.graftcopies(wctx, ctx, base)
2150 return stats
2150 return stats
2151
2151
2152
2152
2153 def purge(
2153 def purge(
2154 repo,
2154 repo,
2155 matcher,
2155 matcher,
2156 unknown=True,
2156 unknown=True,
2157 ignored=False,
2157 ignored=False,
2158 removeemptydirs=True,
2158 removeemptydirs=True,
2159 removefiles=True,
2159 removefiles=True,
2160 abortonerror=False,
2160 abortonerror=False,
2161 noop=False,
2161 noop=False,
2162 ):
2162 ):
2163 """Purge the working directory of untracked files.
2163 """Purge the working directory of untracked files.
2164
2164
2165 ``matcher`` is a matcher configured to scan the working directory -
2165 ``matcher`` is a matcher configured to scan the working directory -
2166 potentially a subset.
2166 potentially a subset.
2167
2167
2168 ``unknown`` controls whether unknown files should be purged.
2168 ``unknown`` controls whether unknown files should be purged.
2169
2169
2170 ``ignored`` controls whether ignored files should be purged.
2170 ``ignored`` controls whether ignored files should be purged.
2171
2171
2172 ``removeemptydirs`` controls whether empty directories should be removed.
2172 ``removeemptydirs`` controls whether empty directories should be removed.
2173
2173
2174 ``removefiles`` controls whether files are removed.
2174 ``removefiles`` controls whether files are removed.
2175
2175
2176 ``abortonerror`` causes an exception to be raised if an error occurs
2176 ``abortonerror`` causes an exception to be raised if an error occurs
2177 deleting a file or directory.
2177 deleting a file or directory.
2178
2178
2179 ``noop`` controls whether to actually remove files. If not defined, actions
2179 ``noop`` controls whether to actually remove files. If not defined, actions
2180 will be taken.
2180 will be taken.
2181
2181
2182 Returns an iterable of relative paths in the working directory that were
2182 Returns an iterable of relative paths in the working directory that were
2183 or would be removed.
2183 or would be removed.
2184 """
2184 """
2185
2185
2186 def remove(removefn, path):
2186 def remove(removefn, path):
2187 try:
2187 try:
2188 removefn(path)
2188 removefn(path)
2189 except OSError:
2189 except OSError:
2190 m = _(b'%s cannot be removed') % path
2190 m = _(b'%s cannot be removed') % path
2191 if abortonerror:
2191 if abortonerror:
2192 raise error.Abort(m)
2192 raise error.Abort(m)
2193 else:
2193 else:
2194 repo.ui.warn(_(b'warning: %s\n') % m)
2194 repo.ui.warn(_(b'warning: %s\n') % m)
2195
2195
2196 # There's no API to copy a matcher. So mutate the passed matcher and
2196 # There's no API to copy a matcher. So mutate the passed matcher and
2197 # restore it when we're done.
2197 # restore it when we're done.
2198 oldtraversedir = matcher.traversedir
2198 oldtraversedir = matcher.traversedir
2199
2199
2200 res = []
2200 res = []
2201
2201
2202 try:
2202 try:
2203 if removeemptydirs:
2203 if removeemptydirs:
2204 directories = []
2204 directories = []
2205 matcher.traversedir = directories.append
2205 matcher.traversedir = directories.append
2206
2206
2207 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2207 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2208
2208
2209 if removefiles:
2209 if removefiles:
2210 for f in sorted(status.unknown + status.ignored):
2210 for f in sorted(status.unknown + status.ignored):
2211 if not noop:
2211 if not noop:
2212 repo.ui.note(_(b'removing file %s\n') % f)
2212 repo.ui.note(_(b'removing file %s\n') % f)
2213 remove(repo.wvfs.unlink, f)
2213 remove(repo.wvfs.unlink, f)
2214 res.append(f)
2214 res.append(f)
2215
2215
2216 if removeemptydirs:
2216 if removeemptydirs:
2217 for f in sorted(directories, reverse=True):
2217 for f in sorted(directories, reverse=True):
2218 if matcher(f) and not repo.wvfs.listdir(f):
2218 if matcher(f) and not repo.wvfs.listdir(f):
2219 if not noop:
2219 if not noop:
2220 repo.ui.note(_(b'removing directory %s\n') % f)
2220 repo.ui.note(_(b'removing directory %s\n') % f)
2221 remove(repo.wvfs.rmdir, f)
2221 remove(repo.wvfs.rmdir, f)
2222 res.append(f)
2222 res.append(f)
2223
2223
2224 return res
2224 return res
2225
2225
2226 finally:
2226 finally:
2227 matcher.traversedir = oldtraversedir
2227 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now