##// END OF EJS Templates
merge: pass mergeresult obj instead of actions dict in _resolvetrivial()...
Pulkit Goyal -
r45843:00e9c5ed default
parent child Browse files
Show More
@@ -1,2227 +1,2229 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import stat
11 import stat
12 import struct
12 import struct
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 addednodeid,
16 addednodeid,
17 modifiednodeid,
17 modifiednodeid,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 )
20 )
21 from .thirdparty import attr
21 from .thirdparty import attr
22 from . import (
22 from . import (
23 copies,
23 copies,
24 encoding,
24 encoding,
25 error,
25 error,
26 filemerge,
26 filemerge,
27 match as matchmod,
27 match as matchmod,
28 mergestate as mergestatemod,
28 mergestate as mergestatemod,
29 obsutil,
29 obsutil,
30 pathutil,
30 pathutil,
31 pycompat,
31 pycompat,
32 scmutil,
32 scmutil,
33 subrepoutil,
33 subrepoutil,
34 util,
34 util,
35 worker,
35 worker,
36 )
36 )
37
37
38 _pack = struct.pack
38 _pack = struct.pack
39 _unpack = struct.unpack
39 _unpack = struct.unpack
40
40
41
41
42 def _getcheckunknownconfig(repo, section, name):
42 def _getcheckunknownconfig(repo, section, name):
43 config = repo.ui.config(section, name)
43 config = repo.ui.config(section, name)
44 valid = [b'abort', b'ignore', b'warn']
44 valid = [b'abort', b'ignore', b'warn']
45 if config not in valid:
45 if config not in valid:
46 validstr = b', '.join([b"'" + v + b"'" for v in valid])
46 validstr = b', '.join([b"'" + v + b"'" for v in valid])
47 raise error.ConfigError(
47 raise error.ConfigError(
48 _(b"%s.%s not valid ('%s' is none of %s)")
48 _(b"%s.%s not valid ('%s' is none of %s)")
49 % (section, name, config, validstr)
49 % (section, name, config, validstr)
50 )
50 )
51 return config
51 return config
52
52
53
53
54 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
54 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
55 if wctx.isinmemory():
55 if wctx.isinmemory():
56 # Nothing to do in IMM because nothing in the "working copy" can be an
56 # Nothing to do in IMM because nothing in the "working copy" can be an
57 # unknown file.
57 # unknown file.
58 #
58 #
59 # Note that we should bail out here, not in ``_checkunknownfiles()``,
59 # Note that we should bail out here, not in ``_checkunknownfiles()``,
60 # because that function does other useful work.
60 # because that function does other useful work.
61 return False
61 return False
62
62
63 if f2 is None:
63 if f2 is None:
64 f2 = f
64 f2 = f
65 return (
65 return (
66 repo.wvfs.audit.check(f)
66 repo.wvfs.audit.check(f)
67 and repo.wvfs.isfileorlink(f)
67 and repo.wvfs.isfileorlink(f)
68 and repo.dirstate.normalize(f) not in repo.dirstate
68 and repo.dirstate.normalize(f) not in repo.dirstate
69 and mctx[f2].cmp(wctx[f])
69 and mctx[f2].cmp(wctx[f])
70 )
70 )
71
71
72
72
73 class _unknowndirschecker(object):
73 class _unknowndirschecker(object):
74 """
74 """
75 Look for any unknown files or directories that may have a path conflict
75 Look for any unknown files or directories that may have a path conflict
76 with a file. If any path prefix of the file exists as a file or link,
76 with a file. If any path prefix of the file exists as a file or link,
77 then it conflicts. If the file itself is a directory that contains any
77 then it conflicts. If the file itself is a directory that contains any
78 file that is not tracked, then it conflicts.
78 file that is not tracked, then it conflicts.
79
79
80 Returns the shortest path at which a conflict occurs, or None if there is
80 Returns the shortest path at which a conflict occurs, or None if there is
81 no conflict.
81 no conflict.
82 """
82 """
83
83
84 def __init__(self):
84 def __init__(self):
85 # A set of paths known to be good. This prevents repeated checking of
85 # A set of paths known to be good. This prevents repeated checking of
86 # dirs. It will be updated with any new dirs that are checked and found
86 # dirs. It will be updated with any new dirs that are checked and found
87 # to be safe.
87 # to be safe.
88 self._unknowndircache = set()
88 self._unknowndircache = set()
89
89
90 # A set of paths that are known to be absent. This prevents repeated
90 # A set of paths that are known to be absent. This prevents repeated
91 # checking of subdirectories that are known not to exist. It will be
91 # checking of subdirectories that are known not to exist. It will be
92 # updated with any new dirs that are checked and found to be absent.
92 # updated with any new dirs that are checked and found to be absent.
93 self._missingdircache = set()
93 self._missingdircache = set()
94
94
95 def __call__(self, repo, wctx, f):
95 def __call__(self, repo, wctx, f):
96 if wctx.isinmemory():
96 if wctx.isinmemory():
97 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
97 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
98 return False
98 return False
99
99
100 # Check for path prefixes that exist as unknown files.
100 # Check for path prefixes that exist as unknown files.
101 for p in reversed(list(pathutil.finddirs(f))):
101 for p in reversed(list(pathutil.finddirs(f))):
102 if p in self._missingdircache:
102 if p in self._missingdircache:
103 return
103 return
104 if p in self._unknowndircache:
104 if p in self._unknowndircache:
105 continue
105 continue
106 if repo.wvfs.audit.check(p):
106 if repo.wvfs.audit.check(p):
107 if (
107 if (
108 repo.wvfs.isfileorlink(p)
108 repo.wvfs.isfileorlink(p)
109 and repo.dirstate.normalize(p) not in repo.dirstate
109 and repo.dirstate.normalize(p) not in repo.dirstate
110 ):
110 ):
111 return p
111 return p
112 if not repo.wvfs.lexists(p):
112 if not repo.wvfs.lexists(p):
113 self._missingdircache.add(p)
113 self._missingdircache.add(p)
114 return
114 return
115 self._unknowndircache.add(p)
115 self._unknowndircache.add(p)
116
116
117 # Check if the file conflicts with a directory containing unknown files.
117 # Check if the file conflicts with a directory containing unknown files.
118 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
118 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
119 # Does the directory contain any files that are not in the dirstate?
119 # Does the directory contain any files that are not in the dirstate?
120 for p, dirs, files in repo.wvfs.walk(f):
120 for p, dirs, files in repo.wvfs.walk(f):
121 for fn in files:
121 for fn in files:
122 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
122 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
123 relf = repo.dirstate.normalize(relf, isknown=True)
123 relf = repo.dirstate.normalize(relf, isknown=True)
124 if relf not in repo.dirstate:
124 if relf not in repo.dirstate:
125 return f
125 return f
126 return None
126 return None
127
127
128
128
129 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
129 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
130 """
130 """
131 Considers any actions that care about the presence of conflicting unknown
131 Considers any actions that care about the presence of conflicting unknown
132 files. For some actions, the result is to abort; for others, it is to
132 files. For some actions, the result is to abort; for others, it is to
133 choose a different action.
133 choose a different action.
134 """
134 """
135 fileconflicts = set()
135 fileconflicts = set()
136 pathconflicts = set()
136 pathconflicts = set()
137 warnconflicts = set()
137 warnconflicts = set()
138 abortconflicts = set()
138 abortconflicts = set()
139 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
139 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
140 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
140 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
141 pathconfig = repo.ui.configbool(
141 pathconfig = repo.ui.configbool(
142 b'experimental', b'merge.checkpathconflicts'
142 b'experimental', b'merge.checkpathconflicts'
143 )
143 )
144 if not force:
144 if not force:
145
145
146 def collectconflicts(conflicts, config):
146 def collectconflicts(conflicts, config):
147 if config == b'abort':
147 if config == b'abort':
148 abortconflicts.update(conflicts)
148 abortconflicts.update(conflicts)
149 elif config == b'warn':
149 elif config == b'warn':
150 warnconflicts.update(conflicts)
150 warnconflicts.update(conflicts)
151
151
152 checkunknowndirs = _unknowndirschecker()
152 checkunknowndirs = _unknowndirschecker()
153 for f, (m, args, msg) in pycompat.iteritems(actions):
153 for f, (m, args, msg) in pycompat.iteritems(actions):
154 if m in (
154 if m in (
155 mergestatemod.ACTION_CREATED,
155 mergestatemod.ACTION_CREATED,
156 mergestatemod.ACTION_DELETED_CHANGED,
156 mergestatemod.ACTION_DELETED_CHANGED,
157 ):
157 ):
158 if _checkunknownfile(repo, wctx, mctx, f):
158 if _checkunknownfile(repo, wctx, mctx, f):
159 fileconflicts.add(f)
159 fileconflicts.add(f)
160 elif pathconfig and f not in wctx:
160 elif pathconfig and f not in wctx:
161 path = checkunknowndirs(repo, wctx, f)
161 path = checkunknowndirs(repo, wctx, f)
162 if path is not None:
162 if path is not None:
163 pathconflicts.add(path)
163 pathconflicts.add(path)
164 elif m == mergestatemod.ACTION_LOCAL_DIR_RENAME_GET:
164 elif m == mergestatemod.ACTION_LOCAL_DIR_RENAME_GET:
165 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
165 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
166 fileconflicts.add(f)
166 fileconflicts.add(f)
167
167
168 allconflicts = fileconflicts | pathconflicts
168 allconflicts = fileconflicts | pathconflicts
169 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
169 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
170 unknownconflicts = allconflicts - ignoredconflicts
170 unknownconflicts = allconflicts - ignoredconflicts
171 collectconflicts(ignoredconflicts, ignoredconfig)
171 collectconflicts(ignoredconflicts, ignoredconfig)
172 collectconflicts(unknownconflicts, unknownconfig)
172 collectconflicts(unknownconflicts, unknownconfig)
173 else:
173 else:
174 for f, (m, args, msg) in pycompat.iteritems(actions):
174 for f, (m, args, msg) in pycompat.iteritems(actions):
175 if m == mergestatemod.ACTION_CREATED_MERGE:
175 if m == mergestatemod.ACTION_CREATED_MERGE:
176 fl2, anc = args
176 fl2, anc = args
177 different = _checkunknownfile(repo, wctx, mctx, f)
177 different = _checkunknownfile(repo, wctx, mctx, f)
178 if repo.dirstate._ignore(f):
178 if repo.dirstate._ignore(f):
179 config = ignoredconfig
179 config = ignoredconfig
180 else:
180 else:
181 config = unknownconfig
181 config = unknownconfig
182
182
183 # The behavior when force is True is described by this table:
183 # The behavior when force is True is described by this table:
184 # config different mergeforce | action backup
184 # config different mergeforce | action backup
185 # * n * | get n
185 # * n * | get n
186 # * y y | merge -
186 # * y y | merge -
187 # abort y n | merge - (1)
187 # abort y n | merge - (1)
188 # warn y n | warn + get y
188 # warn y n | warn + get y
189 # ignore y n | get y
189 # ignore y n | get y
190 #
190 #
191 # (1) this is probably the wrong behavior here -- we should
191 # (1) this is probably the wrong behavior here -- we should
192 # probably abort, but some actions like rebases currently
192 # probably abort, but some actions like rebases currently
193 # don't like an abort happening in the middle of
193 # don't like an abort happening in the middle of
194 # merge.update.
194 # merge.update.
195 if not different:
195 if not different:
196 actions[f] = (
196 actions[f] = (
197 mergestatemod.ACTION_GET,
197 mergestatemod.ACTION_GET,
198 (fl2, False),
198 (fl2, False),
199 b'remote created',
199 b'remote created',
200 )
200 )
201 elif mergeforce or config == b'abort':
201 elif mergeforce or config == b'abort':
202 actions[f] = (
202 actions[f] = (
203 mergestatemod.ACTION_MERGE,
203 mergestatemod.ACTION_MERGE,
204 (f, f, None, False, anc),
204 (f, f, None, False, anc),
205 b'remote differs from untracked local',
205 b'remote differs from untracked local',
206 )
206 )
207 elif config == b'abort':
207 elif config == b'abort':
208 abortconflicts.add(f)
208 abortconflicts.add(f)
209 else:
209 else:
210 if config == b'warn':
210 if config == b'warn':
211 warnconflicts.add(f)
211 warnconflicts.add(f)
212 actions[f] = (
212 actions[f] = (
213 mergestatemod.ACTION_GET,
213 mergestatemod.ACTION_GET,
214 (fl2, True),
214 (fl2, True),
215 b'remote created',
215 b'remote created',
216 )
216 )
217
217
218 for f in sorted(abortconflicts):
218 for f in sorted(abortconflicts):
219 warn = repo.ui.warn
219 warn = repo.ui.warn
220 if f in pathconflicts:
220 if f in pathconflicts:
221 if repo.wvfs.isfileorlink(f):
221 if repo.wvfs.isfileorlink(f):
222 warn(_(b"%s: untracked file conflicts with directory\n") % f)
222 warn(_(b"%s: untracked file conflicts with directory\n") % f)
223 else:
223 else:
224 warn(_(b"%s: untracked directory conflicts with file\n") % f)
224 warn(_(b"%s: untracked directory conflicts with file\n") % f)
225 else:
225 else:
226 warn(_(b"%s: untracked file differs\n") % f)
226 warn(_(b"%s: untracked file differs\n") % f)
227 if abortconflicts:
227 if abortconflicts:
228 raise error.Abort(
228 raise error.Abort(
229 _(
229 _(
230 b"untracked files in working directory "
230 b"untracked files in working directory "
231 b"differ from files in requested revision"
231 b"differ from files in requested revision"
232 )
232 )
233 )
233 )
234
234
235 for f in sorted(warnconflicts):
235 for f in sorted(warnconflicts):
236 if repo.wvfs.isfileorlink(f):
236 if repo.wvfs.isfileorlink(f):
237 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
237 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
238 else:
238 else:
239 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
239 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
240
240
241 for f, (m, args, msg) in pycompat.iteritems(actions):
241 for f, (m, args, msg) in pycompat.iteritems(actions):
242 if m == mergestatemod.ACTION_CREATED:
242 if m == mergestatemod.ACTION_CREATED:
243 backup = (
243 backup = (
244 f in fileconflicts
244 f in fileconflicts
245 or f in pathconflicts
245 or f in pathconflicts
246 or any(p in pathconflicts for p in pathutil.finddirs(f))
246 or any(p in pathconflicts for p in pathutil.finddirs(f))
247 )
247 )
248 (flags,) = args
248 (flags,) = args
249 actions[f] = (mergestatemod.ACTION_GET, (flags, backup), msg)
249 actions[f] = (mergestatemod.ACTION_GET, (flags, backup), msg)
250
250
251
251
252 def _forgetremoved(wctx, mctx, branchmerge):
252 def _forgetremoved(wctx, mctx, branchmerge):
253 """
253 """
254 Forget removed files
254 Forget removed files
255
255
256 If we're jumping between revisions (as opposed to merging), and if
256 If we're jumping between revisions (as opposed to merging), and if
257 neither the working directory nor the target rev has the file,
257 neither the working directory nor the target rev has the file,
258 then we need to remove it from the dirstate, to prevent the
258 then we need to remove it from the dirstate, to prevent the
259 dirstate from listing the file when it is no longer in the
259 dirstate from listing the file when it is no longer in the
260 manifest.
260 manifest.
261
261
262 If we're merging, and the other revision has removed a file
262 If we're merging, and the other revision has removed a file
263 that is not present in the working directory, we need to mark it
263 that is not present in the working directory, we need to mark it
264 as removed.
264 as removed.
265 """
265 """
266
266
267 actions = {}
267 actions = {}
268 m = mergestatemod.ACTION_FORGET
268 m = mergestatemod.ACTION_FORGET
269 if branchmerge:
269 if branchmerge:
270 m = mergestatemod.ACTION_REMOVE
270 m = mergestatemod.ACTION_REMOVE
271 for f in wctx.deleted():
271 for f in wctx.deleted():
272 if f not in mctx:
272 if f not in mctx:
273 actions[f] = m, None, b"forget deleted"
273 actions[f] = m, None, b"forget deleted"
274
274
275 if not branchmerge:
275 if not branchmerge:
276 for f in wctx.removed():
276 for f in wctx.removed():
277 if f not in mctx:
277 if f not in mctx:
278 actions[f] = (
278 actions[f] = (
279 mergestatemod.ACTION_FORGET,
279 mergestatemod.ACTION_FORGET,
280 None,
280 None,
281 b"forget removed",
281 b"forget removed",
282 )
282 )
283
283
284 return actions
284 return actions
285
285
286
286
287 def _checkcollision(repo, wmf, actions):
287 def _checkcollision(repo, wmf, actions):
288 """
288 """
289 Check for case-folding collisions.
289 Check for case-folding collisions.
290 """
290 """
291 # If the repo is narrowed, filter out files outside the narrowspec.
291 # If the repo is narrowed, filter out files outside the narrowspec.
292 narrowmatch = repo.narrowmatch()
292 narrowmatch = repo.narrowmatch()
293 if not narrowmatch.always():
293 if not narrowmatch.always():
294 pmmf = set(wmf.walk(narrowmatch))
294 pmmf = set(wmf.walk(narrowmatch))
295 if actions:
295 if actions:
296 narrowactions = {}
296 narrowactions = {}
297 for m, actionsfortype in pycompat.iteritems(actions):
297 for m, actionsfortype in pycompat.iteritems(actions):
298 narrowactions[m] = []
298 narrowactions[m] = []
299 for (f, args, msg) in actionsfortype:
299 for (f, args, msg) in actionsfortype:
300 if narrowmatch(f):
300 if narrowmatch(f):
301 narrowactions[m].append((f, args, msg))
301 narrowactions[m].append((f, args, msg))
302 actions = narrowactions
302 actions = narrowactions
303 else:
303 else:
304 # build provisional merged manifest up
304 # build provisional merged manifest up
305 pmmf = set(wmf)
305 pmmf = set(wmf)
306
306
307 if actions:
307 if actions:
308 # KEEP and EXEC are no-op
308 # KEEP and EXEC are no-op
309 for m in (
309 for m in (
310 mergestatemod.ACTION_ADD,
310 mergestatemod.ACTION_ADD,
311 mergestatemod.ACTION_ADD_MODIFIED,
311 mergestatemod.ACTION_ADD_MODIFIED,
312 mergestatemod.ACTION_FORGET,
312 mergestatemod.ACTION_FORGET,
313 mergestatemod.ACTION_GET,
313 mergestatemod.ACTION_GET,
314 mergestatemod.ACTION_CHANGED_DELETED,
314 mergestatemod.ACTION_CHANGED_DELETED,
315 mergestatemod.ACTION_DELETED_CHANGED,
315 mergestatemod.ACTION_DELETED_CHANGED,
316 ):
316 ):
317 for f, args, msg in actions[m]:
317 for f, args, msg in actions[m]:
318 pmmf.add(f)
318 pmmf.add(f)
319 for f, args, msg in actions[mergestatemod.ACTION_REMOVE]:
319 for f, args, msg in actions[mergestatemod.ACTION_REMOVE]:
320 pmmf.discard(f)
320 pmmf.discard(f)
321 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
321 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
322 f2, flags = args
322 f2, flags = args
323 pmmf.discard(f2)
323 pmmf.discard(f2)
324 pmmf.add(f)
324 pmmf.add(f)
325 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
325 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
326 pmmf.add(f)
326 pmmf.add(f)
327 for f, args, msg in actions[mergestatemod.ACTION_MERGE]:
327 for f, args, msg in actions[mergestatemod.ACTION_MERGE]:
328 f1, f2, fa, move, anc = args
328 f1, f2, fa, move, anc = args
329 if move:
329 if move:
330 pmmf.discard(f1)
330 pmmf.discard(f1)
331 pmmf.add(f)
331 pmmf.add(f)
332
332
333 # check case-folding collision in provisional merged manifest
333 # check case-folding collision in provisional merged manifest
334 foldmap = {}
334 foldmap = {}
335 for f in pmmf:
335 for f in pmmf:
336 fold = util.normcase(f)
336 fold = util.normcase(f)
337 if fold in foldmap:
337 if fold in foldmap:
338 raise error.Abort(
338 raise error.Abort(
339 _(b"case-folding collision between %s and %s")
339 _(b"case-folding collision between %s and %s")
340 % (f, foldmap[fold])
340 % (f, foldmap[fold])
341 )
341 )
342 foldmap[fold] = f
342 foldmap[fold] = f
343
343
344 # check case-folding of directories
344 # check case-folding of directories
345 foldprefix = unfoldprefix = lastfull = b''
345 foldprefix = unfoldprefix = lastfull = b''
346 for fold, f in sorted(foldmap.items()):
346 for fold, f in sorted(foldmap.items()):
347 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
347 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
348 # the folded prefix matches but actual casing is different
348 # the folded prefix matches but actual casing is different
349 raise error.Abort(
349 raise error.Abort(
350 _(b"case-folding collision between %s and directory of %s")
350 _(b"case-folding collision between %s and directory of %s")
351 % (lastfull, f)
351 % (lastfull, f)
352 )
352 )
353 foldprefix = fold + b'/'
353 foldprefix = fold + b'/'
354 unfoldprefix = f + b'/'
354 unfoldprefix = f + b'/'
355 lastfull = f
355 lastfull = f
356
356
357
357
358 def driverpreprocess(repo, ms, wctx, labels=None):
358 def driverpreprocess(repo, ms, wctx, labels=None):
359 """run the preprocess step of the merge driver, if any
359 """run the preprocess step of the merge driver, if any
360
360
361 This is currently not implemented -- it's an extension point."""
361 This is currently not implemented -- it's an extension point."""
362 return True
362 return True
363
363
364
364
365 def driverconclude(repo, ms, wctx, labels=None):
365 def driverconclude(repo, ms, wctx, labels=None):
366 """run the conclude step of the merge driver, if any
366 """run the conclude step of the merge driver, if any
367
367
368 This is currently not implemented -- it's an extension point."""
368 This is currently not implemented -- it's an extension point."""
369 return True
369 return True
370
370
371
371
372 def _filesindirs(repo, manifest, dirs):
372 def _filesindirs(repo, manifest, dirs):
373 """
373 """
374 Generator that yields pairs of all the files in the manifest that are found
374 Generator that yields pairs of all the files in the manifest that are found
375 inside the directories listed in dirs, and which directory they are found
375 inside the directories listed in dirs, and which directory they are found
376 in.
376 in.
377 """
377 """
378 for f in manifest:
378 for f in manifest:
379 for p in pathutil.finddirs(f):
379 for p in pathutil.finddirs(f):
380 if p in dirs:
380 if p in dirs:
381 yield f, p
381 yield f, p
382 break
382 break
383
383
384
384
385 def checkpathconflicts(repo, wctx, mctx, 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, mresult):
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(mresult.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 mresult.removefile(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 mresult.removefile(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)
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, mresult):
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(mresult.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 mresult.addfile(
975 f, mergestatemod.ACTION_REMOVE, None, b'prompt same'
976 )
975 elif (
977 elif (
976 m == mergestatemod.ACTION_DELETED_CHANGED
978 m == mergestatemod.ACTION_DELETED_CHANGED
977 and f in ancestor
979 and f in ancestor
978 and not mctx[f].cmp(ancestor[f])
980 and not mctx[f].cmp(ancestor[f])
979 ):
981 ):
980 # remote did change but ended up with same content
982 # remote did change but ended up with same content
981 del actions[f] # don't get = keep local deleted
983 mresult.removefile(f) # don't get = keep local deleted
982
984
983
985
984 def calculateupdates(
986 def calculateupdates(
985 repo,
987 repo,
986 wctx,
988 wctx,
987 mctx,
989 mctx,
988 ancestors,
990 ancestors,
989 branchmerge,
991 branchmerge,
990 force,
992 force,
991 acceptremote,
993 acceptremote,
992 followcopies,
994 followcopies,
993 matcher=None,
995 matcher=None,
994 mergeforce=False,
996 mergeforce=False,
995 ):
997 ):
996 """
998 """
997 Calculate the actions needed to merge mctx into wctx using ancestors
999 Calculate the actions needed to merge mctx into wctx using ancestors
998
1000
999 Uses manifestmerge() to merge manifest and get list of actions required to
1001 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
1002 perform for merging two manifests. If there are multiple ancestors, uses bid
1001 merge if enabled.
1003 merge if enabled.
1002
1004
1003 Also filters out actions which are unrequired if repository is sparse.
1005 Also filters out actions which are unrequired if repository is sparse.
1004
1006
1005 Returns mergeresult object same as manifestmerge().
1007 Returns mergeresult object same as manifestmerge().
1006 """
1008 """
1007 # Avoid cycle.
1009 # Avoid cycle.
1008 from . import sparse
1010 from . import sparse
1009
1011
1010 mresult = None
1012 mresult = None
1011 if len(ancestors) == 1: # default
1013 if len(ancestors) == 1: # default
1012 mresult = manifestmerge(
1014 mresult = manifestmerge(
1013 repo,
1015 repo,
1014 wctx,
1016 wctx,
1015 mctx,
1017 mctx,
1016 ancestors[0],
1018 ancestors[0],
1017 branchmerge,
1019 branchmerge,
1018 force,
1020 force,
1019 matcher,
1021 matcher,
1020 acceptremote,
1022 acceptremote,
1021 followcopies,
1023 followcopies,
1022 )
1024 )
1023 _checkunknownfiles(repo, wctx, mctx, force, mresult.actions, mergeforce)
1025 _checkunknownfiles(repo, wctx, mctx, force, mresult.actions, mergeforce)
1024
1026
1025 else: # only when merge.preferancestor=* - the default
1027 else: # only when merge.preferancestor=* - the default
1026 repo.ui.note(
1028 repo.ui.note(
1027 _(b"note: merging %s and %s using bids from ancestors %s\n")
1029 _(b"note: merging %s and %s using bids from ancestors %s\n")
1028 % (
1030 % (
1029 wctx,
1031 wctx,
1030 mctx,
1032 mctx,
1031 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1033 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1032 )
1034 )
1033 )
1035 )
1034
1036
1035 # mapping filename to bids (action method to list af actions)
1037 # mapping filename to bids (action method to list af actions)
1036 # {FILENAME1 : BID1, FILENAME2 : BID2}
1038 # {FILENAME1 : BID1, FILENAME2 : BID2}
1037 # BID is another dictionary which contains
1039 # BID is another dictionary which contains
1038 # mapping of following form:
1040 # mapping of following form:
1039 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1041 # {ACTION_X : [info, ..], ACTION_Y : [info, ..]}
1040 fbids = {}
1042 fbids = {}
1041 diverge, renamedelete = None, None
1043 diverge, renamedelete = None, None
1042 for ancestor in ancestors:
1044 for ancestor in ancestors:
1043 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1045 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1044 mresult1 = manifestmerge(
1046 mresult1 = manifestmerge(
1045 repo,
1047 repo,
1046 wctx,
1048 wctx,
1047 mctx,
1049 mctx,
1048 ancestor,
1050 ancestor,
1049 branchmerge,
1051 branchmerge,
1050 force,
1052 force,
1051 matcher,
1053 matcher,
1052 acceptremote,
1054 acceptremote,
1053 followcopies,
1055 followcopies,
1054 forcefulldiff=True,
1056 forcefulldiff=True,
1055 )
1057 )
1056 _checkunknownfiles(
1058 _checkunknownfiles(
1057 repo, wctx, mctx, force, mresult1.actions, mergeforce
1059 repo, wctx, mctx, force, mresult1.actions, mergeforce
1058 )
1060 )
1059
1061
1060 # Track the shortest set of warning on the theory that bid
1062 # Track the shortest set of warning on the theory that bid
1061 # merge will correctly incorporate more information
1063 # merge will correctly incorporate more information
1062 if diverge is None or len(mresult1.diverge) < len(diverge):
1064 if diverge is None or len(mresult1.diverge) < len(diverge):
1063 diverge = mresult1.diverge
1065 diverge = mresult1.diverge
1064 if renamedelete is None or len(renamedelete) < len(
1066 if renamedelete is None or len(renamedelete) < len(
1065 mresult1.renamedelete
1067 mresult1.renamedelete
1066 ):
1068 ):
1067 renamedelete = mresult1.renamedelete
1069 renamedelete = mresult1.renamedelete
1068
1070
1069 for f, a in sorted(pycompat.iteritems(mresult1.actions)):
1071 for f, a in sorted(pycompat.iteritems(mresult1.actions)):
1070 m, args, msg = a
1072 m, args, msg = a
1071 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1073 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1072 if f in fbids:
1074 if f in fbids:
1073 d = fbids[f]
1075 d = fbids[f]
1074 if m in d:
1076 if m in d:
1075 d[m].append(a)
1077 d[m].append(a)
1076 else:
1078 else:
1077 d[m] = [a]
1079 d[m] = [a]
1078 else:
1080 else:
1079 fbids[f] = {m: [a]}
1081 fbids[f] = {m: [a]}
1080
1082
1081 # Call for bids
1083 # Call for bids
1082 # Pick the best bid for each file
1084 # Pick the best bid for each file
1083 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1085 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1084 mresult = mergeresult()
1086 mresult = mergeresult()
1085 for f, bids in sorted(fbids.items()):
1087 for f, bids in sorted(fbids.items()):
1086 # bids is a mapping from action method to list af actions
1088 # bids is a mapping from action method to list af actions
1087 # Consensus?
1089 # Consensus?
1088 if len(bids) == 1: # all bids are the same kind of method
1090 if len(bids) == 1: # all bids are the same kind of method
1089 m, l = list(bids.items())[0]
1091 m, l = list(bids.items())[0]
1090 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1092 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))
1093 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1092 mresult.addfile(f, *l[0])
1094 mresult.addfile(f, *l[0])
1093 continue
1095 continue
1094 # If keep is an option, just do it.
1096 # If keep is an option, just do it.
1095 if mergestatemod.ACTION_KEEP in bids:
1097 if mergestatemod.ACTION_KEEP in bids:
1096 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1098 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1097 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1099 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1098 continue
1100 continue
1099 # If there are gets and they all agree [how could they not?], do it.
1101 # If there are gets and they all agree [how could they not?], do it.
1100 if mergestatemod.ACTION_GET in bids:
1102 if mergestatemod.ACTION_GET in bids:
1101 ga0 = bids[mergestatemod.ACTION_GET][0]
1103 ga0 = bids[mergestatemod.ACTION_GET][0]
1102 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1104 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1103 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1105 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1104 mresult.addfile(f, *ga0)
1106 mresult.addfile(f, *ga0)
1105 continue
1107 continue
1106 # TODO: Consider other simple actions such as mode changes
1108 # TODO: Consider other simple actions such as mode changes
1107 # Handle inefficient democrazy.
1109 # Handle inefficient democrazy.
1108 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1110 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1109 for m, l in sorted(bids.items()):
1111 for m, l in sorted(bids.items()):
1110 for _f, args, msg in l:
1112 for _f, args, msg in l:
1111 repo.ui.note(b' %s -> %s\n' % (msg, m))
1113 repo.ui.note(b' %s -> %s\n' % (msg, m))
1112 # Pick random action. TODO: Instead, prompt user when resolving
1114 # Pick random action. TODO: Instead, prompt user when resolving
1113 m, l = list(bids.items())[0]
1115 m, l = list(bids.items())[0]
1114 repo.ui.warn(
1116 repo.ui.warn(
1115 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1117 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1116 )
1118 )
1117 mresult.addfile(f, *l[0])
1119 mresult.addfile(f, *l[0])
1118 continue
1120 continue
1119 repo.ui.note(_(b'end of auction\n\n'))
1121 repo.ui.note(_(b'end of auction\n\n'))
1120 # TODO: think about commitinfo when bid merge is used
1122 # TODO: think about commitinfo when bid merge is used
1121 mresult.updatevalues(diverge, renamedelete, {})
1123 mresult.updatevalues(diverge, renamedelete, {})
1122
1124
1123 if wctx.rev() is None:
1125 if wctx.rev() is None:
1124 fractions = _forgetremoved(wctx, mctx, branchmerge)
1126 fractions = _forgetremoved(wctx, mctx, branchmerge)
1125 mresult.actions.update(fractions)
1127 mresult.actions.update(fractions)
1126
1128
1127 prunedactions = sparse.filterupdatesactions(
1129 prunedactions = sparse.filterupdatesactions(
1128 repo, wctx, mctx, branchmerge, mresult.actions
1130 repo, wctx, mctx, branchmerge, mresult.actions
1129 )
1131 )
1130 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult.actions)
1132 _resolvetrivial(repo, wctx, mctx, ancestors[0], mresult)
1131
1133
1132 mresult.setactions(prunedactions)
1134 mresult.setactions(prunedactions)
1133 return mresult
1135 return mresult
1134
1136
1135
1137
1136 def _getcwd():
1138 def _getcwd():
1137 try:
1139 try:
1138 return encoding.getcwd()
1140 return encoding.getcwd()
1139 except OSError as err:
1141 except OSError as err:
1140 if err.errno == errno.ENOENT:
1142 if err.errno == errno.ENOENT:
1141 return None
1143 return None
1142 raise
1144 raise
1143
1145
1144
1146
1145 def batchremove(repo, wctx, actions):
1147 def batchremove(repo, wctx, actions):
1146 """apply removes to the working directory
1148 """apply removes to the working directory
1147
1149
1148 yields tuples for progress updates
1150 yields tuples for progress updates
1149 """
1151 """
1150 verbose = repo.ui.verbose
1152 verbose = repo.ui.verbose
1151 cwd = _getcwd()
1153 cwd = _getcwd()
1152 i = 0
1154 i = 0
1153 for f, args, msg in actions:
1155 for f, args, msg in actions:
1154 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1156 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1155 if verbose:
1157 if verbose:
1156 repo.ui.note(_(b"removing %s\n") % f)
1158 repo.ui.note(_(b"removing %s\n") % f)
1157 wctx[f].audit()
1159 wctx[f].audit()
1158 try:
1160 try:
1159 wctx[f].remove(ignoremissing=True)
1161 wctx[f].remove(ignoremissing=True)
1160 except OSError as inst:
1162 except OSError as inst:
1161 repo.ui.warn(
1163 repo.ui.warn(
1162 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1164 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1163 )
1165 )
1164 if i == 100:
1166 if i == 100:
1165 yield i, f
1167 yield i, f
1166 i = 0
1168 i = 0
1167 i += 1
1169 i += 1
1168 if i > 0:
1170 if i > 0:
1169 yield i, f
1171 yield i, f
1170
1172
1171 if cwd and not _getcwd():
1173 if cwd and not _getcwd():
1172 # cwd was removed in the course of removing files; print a helpful
1174 # cwd was removed in the course of removing files; print a helpful
1173 # warning.
1175 # warning.
1174 repo.ui.warn(
1176 repo.ui.warn(
1175 _(
1177 _(
1176 b"current directory was removed\n"
1178 b"current directory was removed\n"
1177 b"(consider changing to repo root: %s)\n"
1179 b"(consider changing to repo root: %s)\n"
1178 )
1180 )
1179 % repo.root
1181 % repo.root
1180 )
1182 )
1181
1183
1182
1184
1183 def batchget(repo, mctx, wctx, wantfiledata, actions):
1185 def batchget(repo, mctx, wctx, wantfiledata, actions):
1184 """apply gets to the working directory
1186 """apply gets to the working directory
1185
1187
1186 mctx is the context to get from
1188 mctx is the context to get from
1187
1189
1188 Yields arbitrarily many (False, tuple) for progress updates, followed by
1190 Yields arbitrarily many (False, tuple) for progress updates, followed by
1189 exactly one (True, filedata). When wantfiledata is false, filedata is an
1191 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,
1192 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1191 mtime) of the file f written for each action.
1193 mtime) of the file f written for each action.
1192 """
1194 """
1193 filedata = {}
1195 filedata = {}
1194 verbose = repo.ui.verbose
1196 verbose = repo.ui.verbose
1195 fctx = mctx.filectx
1197 fctx = mctx.filectx
1196 ui = repo.ui
1198 ui = repo.ui
1197 i = 0
1199 i = 0
1198 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1200 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1199 for f, (flags, backup), msg in actions:
1201 for f, (flags, backup), msg in actions:
1200 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1202 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1201 if verbose:
1203 if verbose:
1202 repo.ui.note(_(b"getting %s\n") % f)
1204 repo.ui.note(_(b"getting %s\n") % f)
1203
1205
1204 if backup:
1206 if backup:
1205 # If a file or directory exists with the same name, back that
1207 # 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
1208 # 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.
1209 # with a directory this file is in, and if so, back that up.
1208 conflicting = f
1210 conflicting = f
1209 if not repo.wvfs.lexists(f):
1211 if not repo.wvfs.lexists(f):
1210 for p in pathutil.finddirs(f):
1212 for p in pathutil.finddirs(f):
1211 if repo.wvfs.isfileorlink(p):
1213 if repo.wvfs.isfileorlink(p):
1212 conflicting = p
1214 conflicting = p
1213 break
1215 break
1214 if repo.wvfs.lexists(conflicting):
1216 if repo.wvfs.lexists(conflicting):
1215 orig = scmutil.backuppath(ui, repo, conflicting)
1217 orig = scmutil.backuppath(ui, repo, conflicting)
1216 util.rename(repo.wjoin(conflicting), orig)
1218 util.rename(repo.wjoin(conflicting), orig)
1217 wfctx = wctx[f]
1219 wfctx = wctx[f]
1218 wfctx.clearunknown()
1220 wfctx.clearunknown()
1219 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1221 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1220 size = wfctx.write(
1222 size = wfctx.write(
1221 fctx(f).data(),
1223 fctx(f).data(),
1222 flags,
1224 flags,
1223 backgroundclose=True,
1225 backgroundclose=True,
1224 atomictemp=atomictemp,
1226 atomictemp=atomictemp,
1225 )
1227 )
1226 if wantfiledata:
1228 if wantfiledata:
1227 s = wfctx.lstat()
1229 s = wfctx.lstat()
1228 mode = s.st_mode
1230 mode = s.st_mode
1229 mtime = s[stat.ST_MTIME]
1231 mtime = s[stat.ST_MTIME]
1230 filedata[f] = (mode, size, mtime) # for dirstate.normal
1232 filedata[f] = (mode, size, mtime) # for dirstate.normal
1231 if i == 100:
1233 if i == 100:
1232 yield False, (i, f)
1234 yield False, (i, f)
1233 i = 0
1235 i = 0
1234 i += 1
1236 i += 1
1235 if i > 0:
1237 if i > 0:
1236 yield False, (i, f)
1238 yield False, (i, f)
1237 yield True, filedata
1239 yield True, filedata
1238
1240
1239
1241
1240 def _prefetchfiles(repo, ctx, actions):
1242 def _prefetchfiles(repo, ctx, actions):
1241 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1243 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1242 of merge actions. ``ctx`` is the context being merged in."""
1244 of merge actions. ``ctx`` is the context being merged in."""
1243
1245
1244 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1246 # 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
1247 # don't touch the context to be merged in. 'cd' is skipped, because
1246 # changed/deleted never resolves to something from the remote side.
1248 # changed/deleted never resolves to something from the remote side.
1247 oplist = [
1249 oplist = [
1248 actions[a]
1250 actions[a]
1249 for a in (
1251 for a in (
1250 mergestatemod.ACTION_GET,
1252 mergestatemod.ACTION_GET,
1251 mergestatemod.ACTION_DELETED_CHANGED,
1253 mergestatemod.ACTION_DELETED_CHANGED,
1252 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1254 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1253 mergestatemod.ACTION_MERGE,
1255 mergestatemod.ACTION_MERGE,
1254 )
1256 )
1255 ]
1257 ]
1256 prefetch = scmutil.prefetchfiles
1258 prefetch = scmutil.prefetchfiles
1257 matchfiles = scmutil.matchfiles
1259 matchfiles = scmutil.matchfiles
1258 prefetch(
1260 prefetch(
1259 repo,
1261 repo,
1260 [
1262 [
1261 (
1263 (
1262 ctx.rev(),
1264 ctx.rev(),
1263 matchfiles(
1265 matchfiles(
1264 repo, [f for sublist in oplist for f, args, msg in sublist]
1266 repo, [f for sublist in oplist for f, args, msg in sublist]
1265 ),
1267 ),
1266 )
1268 )
1267 ],
1269 ],
1268 )
1270 )
1269
1271
1270
1272
1271 @attr.s(frozen=True)
1273 @attr.s(frozen=True)
1272 class updateresult(object):
1274 class updateresult(object):
1273 updatedcount = attr.ib()
1275 updatedcount = attr.ib()
1274 mergedcount = attr.ib()
1276 mergedcount = attr.ib()
1275 removedcount = attr.ib()
1277 removedcount = attr.ib()
1276 unresolvedcount = attr.ib()
1278 unresolvedcount = attr.ib()
1277
1279
1278 def isempty(self):
1280 def isempty(self):
1279 return not (
1281 return not (
1280 self.updatedcount
1282 self.updatedcount
1281 or self.mergedcount
1283 or self.mergedcount
1282 or self.removedcount
1284 or self.removedcount
1283 or self.unresolvedcount
1285 or self.unresolvedcount
1284 )
1286 )
1285
1287
1286
1288
1287 def emptyactions():
1289 def emptyactions():
1288 """create an actions dict, to be populated and passed to applyupdates()"""
1290 """create an actions dict, to be populated and passed to applyupdates()"""
1289 return {
1291 return {
1290 m: []
1292 m: []
1291 for m in (
1293 for m in (
1292 mergestatemod.ACTION_ADD,
1294 mergestatemod.ACTION_ADD,
1293 mergestatemod.ACTION_ADD_MODIFIED,
1295 mergestatemod.ACTION_ADD_MODIFIED,
1294 mergestatemod.ACTION_FORGET,
1296 mergestatemod.ACTION_FORGET,
1295 mergestatemod.ACTION_GET,
1297 mergestatemod.ACTION_GET,
1296 mergestatemod.ACTION_CHANGED_DELETED,
1298 mergestatemod.ACTION_CHANGED_DELETED,
1297 mergestatemod.ACTION_DELETED_CHANGED,
1299 mergestatemod.ACTION_DELETED_CHANGED,
1298 mergestatemod.ACTION_REMOVE,
1300 mergestatemod.ACTION_REMOVE,
1299 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1301 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1300 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1302 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1301 mergestatemod.ACTION_MERGE,
1303 mergestatemod.ACTION_MERGE,
1302 mergestatemod.ACTION_EXEC,
1304 mergestatemod.ACTION_EXEC,
1303 mergestatemod.ACTION_KEEP,
1305 mergestatemod.ACTION_KEEP,
1304 mergestatemod.ACTION_PATH_CONFLICT,
1306 mergestatemod.ACTION_PATH_CONFLICT,
1305 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1307 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1306 )
1308 )
1307 }
1309 }
1308
1310
1309
1311
1310 def applyupdates(
1312 def applyupdates(
1311 repo,
1313 repo,
1312 actions,
1314 actions,
1313 wctx,
1315 wctx,
1314 mctx,
1316 mctx,
1315 overwrite,
1317 overwrite,
1316 wantfiledata,
1318 wantfiledata,
1317 labels=None,
1319 labels=None,
1318 commitinfo=None,
1320 commitinfo=None,
1319 ):
1321 ):
1320 """apply the merge action list to the working directory
1322 """apply the merge action list to the working directory
1321
1323
1322 wctx is the working copy context
1324 wctx is the working copy context
1323 mctx is the context to be merged into the working copy
1325 mctx is the context to be merged into the working copy
1324 commitinfo is a mapping of information which needs to be stored somewhere
1326 commitinfo is a mapping of information which needs to be stored somewhere
1325 (probably mergestate) so that it can be used at commit time.
1327 (probably mergestate) so that it can be used at commit time.
1326
1328
1327 Return a tuple of (counts, filedata), where counts is a tuple
1329 Return a tuple of (counts, filedata), where counts is a tuple
1328 (updated, merged, removed, unresolved) that describes how many
1330 (updated, merged, removed, unresolved) that describes how many
1329 files were affected by the update, and filedata is as described in
1331 files were affected by the update, and filedata is as described in
1330 batchget.
1332 batchget.
1331 """
1333 """
1332
1334
1333 _prefetchfiles(repo, mctx, actions)
1335 _prefetchfiles(repo, mctx, actions)
1334
1336
1335 updated, merged, removed = 0, 0, 0
1337 updated, merged, removed = 0, 0, 0
1336 ms = mergestatemod.mergestate.clean(
1338 ms = mergestatemod.mergestate.clean(
1337 repo, wctx.p1().node(), mctx.node(), labels
1339 repo, wctx.p1().node(), mctx.node(), labels
1338 )
1340 )
1339
1341
1340 if commitinfo is None:
1342 if commitinfo is None:
1341 commitinfo = {}
1343 commitinfo = {}
1342
1344
1343 for f, op in pycompat.iteritems(commitinfo):
1345 for f, op in pycompat.iteritems(commitinfo):
1344 # the other side of filenode was choosen while merging, store this in
1346 # the other side of filenode was choosen while merging, store this in
1345 # mergestate so that it can be reused on commit
1347 # mergestate so that it can be reused on commit
1346 if op == b'other':
1348 if op == b'other':
1347 ms.addmergedother(f)
1349 ms.addmergedother(f)
1348
1350
1349 moves = []
1351 moves = []
1350 for m, l in actions.items():
1352 for m, l in actions.items():
1351 l.sort()
1353 l.sort()
1352
1354
1353 # 'cd' and 'dc' actions are treated like other merge conflicts
1355 # 'cd' and 'dc' actions are treated like other merge conflicts
1354 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1356 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1355 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1357 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1356 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1358 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1357 for f, args, msg in mergeactions:
1359 for f, args, msg in mergeactions:
1358 f1, f2, fa, move, anc = args
1360 f1, f2, fa, move, anc = args
1359 if f == b'.hgsubstate': # merged internally
1361 if f == b'.hgsubstate': # merged internally
1360 continue
1362 continue
1361 if f1 is None:
1363 if f1 is None:
1362 fcl = filemerge.absentfilectx(wctx, fa)
1364 fcl = filemerge.absentfilectx(wctx, fa)
1363 else:
1365 else:
1364 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1366 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1365 fcl = wctx[f1]
1367 fcl = wctx[f1]
1366 if f2 is None:
1368 if f2 is None:
1367 fco = filemerge.absentfilectx(mctx, fa)
1369 fco = filemerge.absentfilectx(mctx, fa)
1368 else:
1370 else:
1369 fco = mctx[f2]
1371 fco = mctx[f2]
1370 actx = repo[anc]
1372 actx = repo[anc]
1371 if fa in actx:
1373 if fa in actx:
1372 fca = actx[fa]
1374 fca = actx[fa]
1373 else:
1375 else:
1374 # TODO: move to absentfilectx
1376 # TODO: move to absentfilectx
1375 fca = repo.filectx(f1, fileid=nullrev)
1377 fca = repo.filectx(f1, fileid=nullrev)
1376 ms.add(fcl, fco, fca, f)
1378 ms.add(fcl, fco, fca, f)
1377 if f1 != f and move:
1379 if f1 != f and move:
1378 moves.append(f1)
1380 moves.append(f1)
1379
1381
1380 # remove renamed files after safely stored
1382 # remove renamed files after safely stored
1381 for f in moves:
1383 for f in moves:
1382 if wctx[f].lexists():
1384 if wctx[f].lexists():
1383 repo.ui.debug(b"removing %s\n" % f)
1385 repo.ui.debug(b"removing %s\n" % f)
1384 wctx[f].audit()
1386 wctx[f].audit()
1385 wctx[f].remove()
1387 wctx[f].remove()
1386
1388
1387 numupdates = sum(
1389 numupdates = sum(
1388 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1390 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1389 )
1391 )
1390 progress = repo.ui.makeprogress(
1392 progress = repo.ui.makeprogress(
1391 _(b'updating'), unit=_(b'files'), total=numupdates
1393 _(b'updating'), unit=_(b'files'), total=numupdates
1392 )
1394 )
1393
1395
1394 if [
1396 if [
1395 a
1397 a
1396 for a in actions[mergestatemod.ACTION_REMOVE]
1398 for a in actions[mergestatemod.ACTION_REMOVE]
1397 if a[0] == b'.hgsubstate'
1399 if a[0] == b'.hgsubstate'
1398 ]:
1400 ]:
1399 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1401 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1400
1402
1401 # record path conflicts
1403 # record path conflicts
1402 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1404 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1403 f1, fo = args
1405 f1, fo = args
1404 s = repo.ui.status
1406 s = repo.ui.status
1405 s(
1407 s(
1406 _(
1408 _(
1407 b"%s: path conflict - a file or link has the same name as a "
1409 b"%s: path conflict - a file or link has the same name as a "
1408 b"directory\n"
1410 b"directory\n"
1409 )
1411 )
1410 % f
1412 % f
1411 )
1413 )
1412 if fo == b'l':
1414 if fo == b'l':
1413 s(_(b"the local file has been renamed to %s\n") % f1)
1415 s(_(b"the local file has been renamed to %s\n") % f1)
1414 else:
1416 else:
1415 s(_(b"the remote file has been renamed to %s\n") % f1)
1417 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)
1418 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1417 ms.addpathconflict(f, f1, fo)
1419 ms.addpathconflict(f, f1, fo)
1418 progress.increment(item=f)
1420 progress.increment(item=f)
1419
1421
1420 # When merging in-memory, we can't support worker processes, so set the
1422 # When merging in-memory, we can't support worker processes, so set the
1421 # per-item cost at 0 in that case.
1423 # per-item cost at 0 in that case.
1422 cost = 0 if wctx.isinmemory() else 0.001
1424 cost = 0 if wctx.isinmemory() else 0.001
1423
1425
1424 # remove in parallel (must come before resolving path conflicts and getting)
1426 # remove in parallel (must come before resolving path conflicts and getting)
1425 prog = worker.worker(
1427 prog = worker.worker(
1426 repo.ui,
1428 repo.ui,
1427 cost,
1429 cost,
1428 batchremove,
1430 batchremove,
1429 (repo, wctx),
1431 (repo, wctx),
1430 actions[mergestatemod.ACTION_REMOVE],
1432 actions[mergestatemod.ACTION_REMOVE],
1431 )
1433 )
1432 for i, item in prog:
1434 for i, item in prog:
1433 progress.increment(step=i, item=item)
1435 progress.increment(step=i, item=item)
1434 removed = len(actions[mergestatemod.ACTION_REMOVE])
1436 removed = len(actions[mergestatemod.ACTION_REMOVE])
1435
1437
1436 # resolve path conflicts (must come before getting)
1438 # resolve path conflicts (must come before getting)
1437 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1439 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1438 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1440 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1439 (f0, origf0) = args
1441 (f0, origf0) = args
1440 if wctx[f0].lexists():
1442 if wctx[f0].lexists():
1441 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1443 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1442 wctx[f].audit()
1444 wctx[f].audit()
1443 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1445 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1444 wctx[f0].remove()
1446 wctx[f0].remove()
1445 progress.increment(item=f)
1447 progress.increment(item=f)
1446
1448
1447 # get in parallel.
1449 # get in parallel.
1448 threadsafe = repo.ui.configbool(
1450 threadsafe = repo.ui.configbool(
1449 b'experimental', b'worker.wdir-get-thread-safe'
1451 b'experimental', b'worker.wdir-get-thread-safe'
1450 )
1452 )
1451 prog = worker.worker(
1453 prog = worker.worker(
1452 repo.ui,
1454 repo.ui,
1453 cost,
1455 cost,
1454 batchget,
1456 batchget,
1455 (repo, mctx, wctx, wantfiledata),
1457 (repo, mctx, wctx, wantfiledata),
1456 actions[mergestatemod.ACTION_GET],
1458 actions[mergestatemod.ACTION_GET],
1457 threadsafe=threadsafe,
1459 threadsafe=threadsafe,
1458 hasretval=True,
1460 hasretval=True,
1459 )
1461 )
1460 getfiledata = {}
1462 getfiledata = {}
1461 for final, res in prog:
1463 for final, res in prog:
1462 if final:
1464 if final:
1463 getfiledata = res
1465 getfiledata = res
1464 else:
1466 else:
1465 i, item = res
1467 i, item = res
1466 progress.increment(step=i, item=item)
1468 progress.increment(step=i, item=item)
1467 updated = len(actions[mergestatemod.ACTION_GET])
1469 updated = len(actions[mergestatemod.ACTION_GET])
1468
1470
1469 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1471 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1470 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1472 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1471
1473
1472 # forget (manifest only, just log it) (must come first)
1474 # forget (manifest only, just log it) (must come first)
1473 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1475 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1474 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1476 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1475 progress.increment(item=f)
1477 progress.increment(item=f)
1476
1478
1477 # re-add (manifest only, just log it)
1479 # re-add (manifest only, just log it)
1478 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1480 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1479 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1481 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1480 progress.increment(item=f)
1482 progress.increment(item=f)
1481
1483
1482 # re-add/mark as modified (manifest only, just log it)
1484 # re-add/mark as modified (manifest only, just log it)
1483 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1485 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1484 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1486 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1485 progress.increment(item=f)
1487 progress.increment(item=f)
1486
1488
1487 # keep (noop, just log it)
1489 # keep (noop, just log it)
1488 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
1490 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
1489 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1491 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1490 # no progress
1492 # no progress
1491
1493
1492 # directory rename, move local
1494 # directory rename, move local
1493 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1495 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1494 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1496 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1495 progress.increment(item=f)
1497 progress.increment(item=f)
1496 f0, flags = args
1498 f0, flags = args
1497 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1499 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1498 wctx[f].audit()
1500 wctx[f].audit()
1499 wctx[f].write(wctx.filectx(f0).data(), flags)
1501 wctx[f].write(wctx.filectx(f0).data(), flags)
1500 wctx[f0].remove()
1502 wctx[f0].remove()
1501 updated += 1
1503 updated += 1
1502
1504
1503 # local directory rename, get
1505 # local directory rename, get
1504 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1506 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1505 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1507 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1506 progress.increment(item=f)
1508 progress.increment(item=f)
1507 f0, flags = args
1509 f0, flags = args
1508 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1510 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1509 wctx[f].write(mctx.filectx(f0).data(), flags)
1511 wctx[f].write(mctx.filectx(f0).data(), flags)
1510 updated += 1
1512 updated += 1
1511
1513
1512 # exec
1514 # exec
1513 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
1515 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
1514 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1516 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1515 progress.increment(item=f)
1517 progress.increment(item=f)
1516 (flags,) = args
1518 (flags,) = args
1517 wctx[f].audit()
1519 wctx[f].audit()
1518 wctx[f].setflags(b'l' in flags, b'x' in flags)
1520 wctx[f].setflags(b'l' in flags, b'x' in flags)
1519 updated += 1
1521 updated += 1
1520
1522
1521 # the ordering is important here -- ms.mergedriver will raise if the merge
1523 # 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
1524 # driver has changed, and we want to be able to bypass it when overwrite is
1523 # True
1525 # True
1524 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1526 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1525
1527
1526 if usemergedriver:
1528 if usemergedriver:
1527 if wctx.isinmemory():
1529 if wctx.isinmemory():
1528 raise error.InMemoryMergeConflictsError(
1530 raise error.InMemoryMergeConflictsError(
1529 b"in-memory merge does not support mergedriver"
1531 b"in-memory merge does not support mergedriver"
1530 )
1532 )
1531 ms.commit()
1533 ms.commit()
1532 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1534 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1533 # the driver might leave some files unresolved
1535 # the driver might leave some files unresolved
1534 unresolvedf = set(ms.unresolved())
1536 unresolvedf = set(ms.unresolved())
1535 if not proceed:
1537 if not proceed:
1536 # XXX setting unresolved to at least 1 is a hack to make sure we
1538 # XXX setting unresolved to at least 1 is a hack to make sure we
1537 # error out
1539 # error out
1538 return updateresult(
1540 return updateresult(
1539 updated, merged, removed, max(len(unresolvedf), 1)
1541 updated, merged, removed, max(len(unresolvedf), 1)
1540 )
1542 )
1541 newactions = []
1543 newactions = []
1542 for f, args, msg in mergeactions:
1544 for f, args, msg in mergeactions:
1543 if f in unresolvedf:
1545 if f in unresolvedf:
1544 newactions.append((f, args, msg))
1546 newactions.append((f, args, msg))
1545 mergeactions = newactions
1547 mergeactions = newactions
1546
1548
1547 try:
1549 try:
1548 # premerge
1550 # premerge
1549 tocomplete = []
1551 tocomplete = []
1550 for f, args, msg in mergeactions:
1552 for f, args, msg in mergeactions:
1551 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1553 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1552 progress.increment(item=f)
1554 progress.increment(item=f)
1553 if f == b'.hgsubstate': # subrepo states need updating
1555 if f == b'.hgsubstate': # subrepo states need updating
1554 subrepoutil.submerge(
1556 subrepoutil.submerge(
1555 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1557 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1556 )
1558 )
1557 continue
1559 continue
1558 wctx[f].audit()
1560 wctx[f].audit()
1559 complete, r = ms.preresolve(f, wctx)
1561 complete, r = ms.preresolve(f, wctx)
1560 if not complete:
1562 if not complete:
1561 numupdates += 1
1563 numupdates += 1
1562 tocomplete.append((f, args, msg))
1564 tocomplete.append((f, args, msg))
1563
1565
1564 # merge
1566 # merge
1565 for f, args, msg in tocomplete:
1567 for f, args, msg in tocomplete:
1566 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1568 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1567 progress.increment(item=f, total=numupdates)
1569 progress.increment(item=f, total=numupdates)
1568 ms.resolve(f, wctx)
1570 ms.resolve(f, wctx)
1569
1571
1570 finally:
1572 finally:
1571 ms.commit()
1573 ms.commit()
1572
1574
1573 unresolved = ms.unresolvedcount()
1575 unresolved = ms.unresolvedcount()
1574
1576
1575 if (
1577 if (
1576 usemergedriver
1578 usemergedriver
1577 and not unresolved
1579 and not unresolved
1578 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1580 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1579 ):
1581 ):
1580 if not driverconclude(repo, ms, wctx, labels=labels):
1582 if not driverconclude(repo, ms, wctx, labels=labels):
1581 # XXX setting unresolved to at least 1 is a hack to make sure we
1583 # XXX setting unresolved to at least 1 is a hack to make sure we
1582 # error out
1584 # error out
1583 unresolved = max(unresolved, 1)
1585 unresolved = max(unresolved, 1)
1584
1586
1585 ms.commit()
1587 ms.commit()
1586
1588
1587 msupdated, msmerged, msremoved = ms.counts()
1589 msupdated, msmerged, msremoved = ms.counts()
1588 updated += msupdated
1590 updated += msupdated
1589 merged += msmerged
1591 merged += msmerged
1590 removed += msremoved
1592 removed += msremoved
1591
1593
1592 extraactions = ms.actions()
1594 extraactions = ms.actions()
1593 if extraactions:
1595 if extraactions:
1594 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
1596 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
1595 for k, acts in pycompat.iteritems(extraactions):
1597 for k, acts in pycompat.iteritems(extraactions):
1596 actions[k].extend(acts)
1598 actions[k].extend(acts)
1597 if k == mergestatemod.ACTION_GET and wantfiledata:
1599 if k == mergestatemod.ACTION_GET and wantfiledata:
1598 # no filedata until mergestate is updated to provide it
1600 # no filedata until mergestate is updated to provide it
1599 for a in acts:
1601 for a in acts:
1600 getfiledata[a[0]] = None
1602 getfiledata[a[0]] = None
1601 # Remove these files from actions[ACTION_MERGE] as well. This is
1603 # Remove these files from actions[ACTION_MERGE] as well. This is
1602 # important because in recordupdates, files in actions[ACTION_MERGE]
1604 # important because in recordupdates, files in actions[ACTION_MERGE]
1603 # are processed after files in other actions, and the merge driver
1605 # are processed after files in other actions, and the merge driver
1604 # might add files to those actions via extraactions above. This can
1606 # might add files to those actions via extraactions above. This can
1605 # lead to a file being recorded twice, with poor results. This is
1607 # lead to a file being recorded twice, with poor results. This is
1606 # especially problematic for actions[ACTION_REMOVE] (currently only
1608 # especially problematic for actions[ACTION_REMOVE] (currently only
1607 # possible with the merge driver in the initial merge process;
1609 # possible with the merge driver in the initial merge process;
1608 # interrupted merges don't go through this flow).
1610 # interrupted merges don't go through this flow).
1609 #
1611 #
1610 # The real fix here is to have indexes by both file and action so
1612 # 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
1613 # that when the action for a file is changed it is automatically
1612 # reflected in the other action lists. But that involves a more
1614 # reflected in the other action lists. But that involves a more
1613 # complex data structure, so this will do for now.
1615 # complex data structure, so this will do for now.
1614 #
1616 #
1615 # We don't need to do the same operation for 'dc' and 'cd' because
1617 # We don't need to do the same operation for 'dc' and 'cd' because
1616 # those lists aren't consulted again.
1618 # those lists aren't consulted again.
1617 mfiles.difference_update(a[0] for a in acts)
1619 mfiles.difference_update(a[0] for a in acts)
1618
1620
1619 actions[mergestatemod.ACTION_MERGE] = [
1621 actions[mergestatemod.ACTION_MERGE] = [
1620 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
1622 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
1621 ]
1623 ]
1622
1624
1623 progress.complete()
1625 progress.complete()
1624 assert len(getfiledata) == (
1626 assert len(getfiledata) == (
1625 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1627 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1626 )
1628 )
1627 return updateresult(updated, merged, removed, unresolved), getfiledata
1629 return updateresult(updated, merged, removed, unresolved), getfiledata
1628
1630
1629
1631
1630 def _advertisefsmonitor(repo, num_gets, p1node):
1632 def _advertisefsmonitor(repo, num_gets, p1node):
1631 # Advertise fsmonitor when its presence could be useful.
1633 # Advertise fsmonitor when its presence could be useful.
1632 #
1634 #
1633 # We only advertise when performing an update from an empty working
1635 # We only advertise when performing an update from an empty working
1634 # directory. This typically only occurs during initial clone.
1636 # directory. This typically only occurs during initial clone.
1635 #
1637 #
1636 # We give users a mechanism to disable the warning in case it is
1638 # We give users a mechanism to disable the warning in case it is
1637 # annoying.
1639 # annoying.
1638 #
1640 #
1639 # We only allow on Linux and MacOS because that's where fsmonitor is
1641 # We only allow on Linux and MacOS because that's where fsmonitor is
1640 # considered stable.
1642 # considered stable.
1641 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1643 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1642 fsmonitorthreshold = repo.ui.configint(
1644 fsmonitorthreshold = repo.ui.configint(
1643 b'fsmonitor', b'warn_update_file_count'
1645 b'fsmonitor', b'warn_update_file_count'
1644 )
1646 )
1645 try:
1647 try:
1646 # avoid cycle: extensions -> cmdutil -> merge
1648 # avoid cycle: extensions -> cmdutil -> merge
1647 from . import extensions
1649 from . import extensions
1648
1650
1649 extensions.find(b'fsmonitor')
1651 extensions.find(b'fsmonitor')
1650 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1652 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1651 # We intentionally don't look at whether fsmonitor has disabled
1653 # We intentionally don't look at whether fsmonitor has disabled
1652 # itself because a) fsmonitor may have already printed a warning
1654 # itself because a) fsmonitor may have already printed a warning
1653 # b) we only care about the config state here.
1655 # b) we only care about the config state here.
1654 except KeyError:
1656 except KeyError:
1655 fsmonitorenabled = False
1657 fsmonitorenabled = False
1656
1658
1657 if (
1659 if (
1658 fsmonitorwarning
1660 fsmonitorwarning
1659 and not fsmonitorenabled
1661 and not fsmonitorenabled
1660 and p1node == nullid
1662 and p1node == nullid
1661 and num_gets >= fsmonitorthreshold
1663 and num_gets >= fsmonitorthreshold
1662 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1664 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1663 ):
1665 ):
1664 repo.ui.warn(
1666 repo.ui.warn(
1665 _(
1667 _(
1666 b'(warning: large working directory being used without '
1668 b'(warning: large working directory being used without '
1667 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1669 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1668 b'see "hg help -e fsmonitor")\n'
1670 b'see "hg help -e fsmonitor")\n'
1669 )
1671 )
1670 )
1672 )
1671
1673
1672
1674
1673 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1675 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1674 UPDATECHECK_NONE = b'none'
1676 UPDATECHECK_NONE = b'none'
1675 UPDATECHECK_LINEAR = b'linear'
1677 UPDATECHECK_LINEAR = b'linear'
1676 UPDATECHECK_NO_CONFLICT = b'noconflict'
1678 UPDATECHECK_NO_CONFLICT = b'noconflict'
1677
1679
1678
1680
1679 def update(
1681 def update(
1680 repo,
1682 repo,
1681 node,
1683 node,
1682 branchmerge,
1684 branchmerge,
1683 force,
1685 force,
1684 ancestor=None,
1686 ancestor=None,
1685 mergeancestor=False,
1687 mergeancestor=False,
1686 labels=None,
1688 labels=None,
1687 matcher=None,
1689 matcher=None,
1688 mergeforce=False,
1690 mergeforce=False,
1689 updatedirstate=True,
1691 updatedirstate=True,
1690 updatecheck=None,
1692 updatecheck=None,
1691 wc=None,
1693 wc=None,
1692 ):
1694 ):
1693 """
1695 """
1694 Perform a merge between the working directory and the given node
1696 Perform a merge between the working directory and the given node
1695
1697
1696 node = the node to update to
1698 node = the node to update to
1697 branchmerge = whether to merge between branches
1699 branchmerge = whether to merge between branches
1698 force = whether to force branch merging or file overwriting
1700 force = whether to force branch merging or file overwriting
1699 matcher = a matcher to filter file lists (dirstate not updated)
1701 matcher = a matcher to filter file lists (dirstate not updated)
1700 mergeancestor = whether it is merging with an ancestor. If true,
1702 mergeancestor = whether it is merging with an ancestor. If true,
1701 we should accept the incoming changes for any prompts that occur.
1703 we should accept the incoming changes for any prompts that occur.
1702 If false, merging with an ancestor (fast-forward) is only allowed
1704 If false, merging with an ancestor (fast-forward) is only allowed
1703 between different named branches. This flag is used by rebase extension
1705 between different named branches. This flag is used by rebase extension
1704 as a temporary fix and should be avoided in general.
1706 as a temporary fix and should be avoided in general.
1705 labels = labels to use for base, local and other
1707 labels = labels to use for base, local and other
1706 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1708 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1707 this is True, then 'force' should be True as well.
1709 this is True, then 'force' should be True as well.
1708
1710
1709 The table below shows all the behaviors of the update command given the
1711 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
1712 -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
1713 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
1714 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
1715 option doesn't exist on the command line, but represents the
1714 experimental.updatecheck=noconflict option.
1716 experimental.updatecheck=noconflict option.
1715
1717
1716 This logic is tested by test-update-branches.t.
1718 This logic is tested by test-update-branches.t.
1717
1719
1718 -c -C -n -m dirty rev linear | result
1720 -c -C -n -m dirty rev linear | result
1719 y y * * * * * | (1)
1721 y y * * * * * | (1)
1720 y * y * * * * | (1)
1722 y * y * * * * | (1)
1721 y * * y * * * | (1)
1723 y * * y * * * | (1)
1722 * y y * * * * | (1)
1724 * y y * * * * | (1)
1723 * y * y * * * | (1)
1725 * y * y * * * | (1)
1724 * * y y * * * | (1)
1726 * * y y * * * | (1)
1725 * * * * * n n | x
1727 * * * * * n n | x
1726 * * * * n * * | ok
1728 * * * * n * * | ok
1727 n n n n y * y | merge
1729 n n n n y * y | merge
1728 n n n n y y n | (2)
1730 n n n n y y n | (2)
1729 n n n y y * * | merge
1731 n n n y y * * | merge
1730 n n y n y * * | merge if no conflict
1732 n n y n y * * | merge if no conflict
1731 n y n n y * * | discard
1733 n y n n y * * | discard
1732 y n n n y * * | (3)
1734 y n n n y * * | (3)
1733
1735
1734 x = can't happen
1736 x = can't happen
1735 * = don't-care
1737 * = don't-care
1736 1 = incompatible options (checked in commands.py)
1738 1 = incompatible options (checked in commands.py)
1737 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1739 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1738 3 = abort: uncommitted changes (checked in commands.py)
1740 3 = abort: uncommitted changes (checked in commands.py)
1739
1741
1740 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1742 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1741 to repo[None] if None is passed.
1743 to repo[None] if None is passed.
1742
1744
1743 Return the same tuple as applyupdates().
1745 Return the same tuple as applyupdates().
1744 """
1746 """
1745 # Avoid cycle.
1747 # Avoid cycle.
1746 from . import sparse
1748 from . import sparse
1747
1749
1748 # This function used to find the default destination if node was None, but
1750 # This function used to find the default destination if node was None, but
1749 # that's now in destutil.py.
1751 # that's now in destutil.py.
1750 assert node is not None
1752 assert node is not None
1751 if not branchmerge and not force:
1753 if not branchmerge and not force:
1752 # TODO: remove the default once all callers that pass branchmerge=False
1754 # 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
1755 # and force=False pass a value for updatecheck. We may want to allow
1754 # updatecheck='abort' to better suppport some of these callers.
1756 # updatecheck='abort' to better suppport some of these callers.
1755 if updatecheck is None:
1757 if updatecheck is None:
1756 updatecheck = UPDATECHECK_LINEAR
1758 updatecheck = UPDATECHECK_LINEAR
1757 if updatecheck not in (
1759 if updatecheck not in (
1758 UPDATECHECK_NONE,
1760 UPDATECHECK_NONE,
1759 UPDATECHECK_LINEAR,
1761 UPDATECHECK_LINEAR,
1760 UPDATECHECK_NO_CONFLICT,
1762 UPDATECHECK_NO_CONFLICT,
1761 ):
1763 ):
1762 raise ValueError(
1764 raise ValueError(
1763 r'Invalid updatecheck %r (can accept %r)'
1765 r'Invalid updatecheck %r (can accept %r)'
1764 % (
1766 % (
1765 updatecheck,
1767 updatecheck,
1766 (
1768 (
1767 UPDATECHECK_NONE,
1769 UPDATECHECK_NONE,
1768 UPDATECHECK_LINEAR,
1770 UPDATECHECK_LINEAR,
1769 UPDATECHECK_NO_CONFLICT,
1771 UPDATECHECK_NO_CONFLICT,
1770 ),
1772 ),
1771 )
1773 )
1772 )
1774 )
1773 if wc is not None and wc.isinmemory():
1775 if wc is not None and wc.isinmemory():
1774 maybe_wlock = util.nullcontextmanager()
1776 maybe_wlock = util.nullcontextmanager()
1775 else:
1777 else:
1776 maybe_wlock = repo.wlock()
1778 maybe_wlock = repo.wlock()
1777 with maybe_wlock:
1779 with maybe_wlock:
1778 if wc is None:
1780 if wc is None:
1779 wc = repo[None]
1781 wc = repo[None]
1780 pl = wc.parents()
1782 pl = wc.parents()
1781 p1 = pl[0]
1783 p1 = pl[0]
1782 p2 = repo[node]
1784 p2 = repo[node]
1783 if ancestor is not None:
1785 if ancestor is not None:
1784 pas = [repo[ancestor]]
1786 pas = [repo[ancestor]]
1785 else:
1787 else:
1786 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1788 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1787 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1789 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1788 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1790 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1789 else:
1791 else:
1790 pas = [p1.ancestor(p2, warn=branchmerge)]
1792 pas = [p1.ancestor(p2, warn=branchmerge)]
1791
1793
1792 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1794 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1793
1795
1794 overwrite = force and not branchmerge
1796 overwrite = force and not branchmerge
1795 ### check phase
1797 ### check phase
1796 if not overwrite:
1798 if not overwrite:
1797 if len(pl) > 1:
1799 if len(pl) > 1:
1798 raise error.Abort(_(b"outstanding uncommitted merge"))
1800 raise error.Abort(_(b"outstanding uncommitted merge"))
1799 ms = mergestatemod.mergestate.read(repo)
1801 ms = mergestatemod.mergestate.read(repo)
1800 if list(ms.unresolved()):
1802 if list(ms.unresolved()):
1801 raise error.Abort(
1803 raise error.Abort(
1802 _(b"outstanding merge conflicts"),
1804 _(b"outstanding merge conflicts"),
1803 hint=_(b"use 'hg resolve' to resolve"),
1805 hint=_(b"use 'hg resolve' to resolve"),
1804 )
1806 )
1805 if branchmerge:
1807 if branchmerge:
1806 if pas == [p2]:
1808 if pas == [p2]:
1807 raise error.Abort(
1809 raise error.Abort(
1808 _(
1810 _(
1809 b"merging with a working directory ancestor"
1811 b"merging with a working directory ancestor"
1810 b" has no effect"
1812 b" has no effect"
1811 )
1813 )
1812 )
1814 )
1813 elif pas == [p1]:
1815 elif pas == [p1]:
1814 if not mergeancestor and wc.branch() == p2.branch():
1816 if not mergeancestor and wc.branch() == p2.branch():
1815 raise error.Abort(
1817 raise error.Abort(
1816 _(b"nothing to merge"),
1818 _(b"nothing to merge"),
1817 hint=_(b"use 'hg update' or check 'hg heads'"),
1819 hint=_(b"use 'hg update' or check 'hg heads'"),
1818 )
1820 )
1819 if not force and (wc.files() or wc.deleted()):
1821 if not force and (wc.files() or wc.deleted()):
1820 raise error.Abort(
1822 raise error.Abort(
1821 _(b"uncommitted changes"),
1823 _(b"uncommitted changes"),
1822 hint=_(b"use 'hg status' to list changes"),
1824 hint=_(b"use 'hg status' to list changes"),
1823 )
1825 )
1824 if not wc.isinmemory():
1826 if not wc.isinmemory():
1825 for s in sorted(wc.substate):
1827 for s in sorted(wc.substate):
1826 wc.sub(s).bailifchanged()
1828 wc.sub(s).bailifchanged()
1827
1829
1828 elif not overwrite:
1830 elif not overwrite:
1829 if p1 == p2: # no-op update
1831 if p1 == p2: # no-op update
1830 # call the hooks and exit early
1832 # call the hooks and exit early
1831 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1833 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1832 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1834 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1833 return updateresult(0, 0, 0, 0)
1835 return updateresult(0, 0, 0, 0)
1834
1836
1835 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1837 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1836 [p1],
1838 [p1],
1837 [p2],
1839 [p2],
1838 ): # nonlinear
1840 ): # nonlinear
1839 dirty = wc.dirty(missing=True)
1841 dirty = wc.dirty(missing=True)
1840 if dirty:
1842 if dirty:
1841 # Branching is a bit strange to ensure we do the minimal
1843 # Branching is a bit strange to ensure we do the minimal
1842 # amount of call to obsutil.foreground.
1844 # amount of call to obsutil.foreground.
1843 foreground = obsutil.foreground(repo, [p1.node()])
1845 foreground = obsutil.foreground(repo, [p1.node()])
1844 # note: the <node> variable contains a random identifier
1846 # note: the <node> variable contains a random identifier
1845 if repo[node].node() in foreground:
1847 if repo[node].node() in foreground:
1846 pass # allow updating to successors
1848 pass # allow updating to successors
1847 else:
1849 else:
1848 msg = _(b"uncommitted changes")
1850 msg = _(b"uncommitted changes")
1849 hint = _(b"commit or update --clean to discard changes")
1851 hint = _(b"commit or update --clean to discard changes")
1850 raise error.UpdateAbort(msg, hint=hint)
1852 raise error.UpdateAbort(msg, hint=hint)
1851 else:
1853 else:
1852 # Allow jumping branches if clean and specific rev given
1854 # Allow jumping branches if clean and specific rev given
1853 pass
1855 pass
1854
1856
1855 if overwrite:
1857 if overwrite:
1856 pas = [wc]
1858 pas = [wc]
1857 elif not branchmerge:
1859 elif not branchmerge:
1858 pas = [p1]
1860 pas = [p1]
1859
1861
1860 # deprecated config: merge.followcopies
1862 # deprecated config: merge.followcopies
1861 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1863 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1862 if overwrite:
1864 if overwrite:
1863 followcopies = False
1865 followcopies = False
1864 elif not pas[0]:
1866 elif not pas[0]:
1865 followcopies = False
1867 followcopies = False
1866 if not branchmerge and not wc.dirty(missing=True):
1868 if not branchmerge and not wc.dirty(missing=True):
1867 followcopies = False
1869 followcopies = False
1868
1870
1869 ### calculate phase
1871 ### calculate phase
1870 mresult = calculateupdates(
1872 mresult = calculateupdates(
1871 repo,
1873 repo,
1872 wc,
1874 wc,
1873 p2,
1875 p2,
1874 pas,
1876 pas,
1875 branchmerge,
1877 branchmerge,
1876 force,
1878 force,
1877 mergeancestor,
1879 mergeancestor,
1878 followcopies,
1880 followcopies,
1879 matcher=matcher,
1881 matcher=matcher,
1880 mergeforce=mergeforce,
1882 mergeforce=mergeforce,
1881 )
1883 )
1882
1884
1883 if updatecheck == UPDATECHECK_NO_CONFLICT:
1885 if updatecheck == UPDATECHECK_NO_CONFLICT:
1884 if mresult.hasconflicts():
1886 if mresult.hasconflicts():
1885 msg = _(b"conflicting changes")
1887 msg = _(b"conflicting changes")
1886 hint = _(b"commit or update --clean to discard changes")
1888 hint = _(b"commit or update --clean to discard changes")
1887 raise error.Abort(msg, hint=hint)
1889 raise error.Abort(msg, hint=hint)
1888
1890
1889 # Prompt and create actions. Most of this is in the resolve phase
1891 # Prompt and create actions. Most of this is in the resolve phase
1890 # already, but we can't handle .hgsubstate in filemerge or
1892 # already, but we can't handle .hgsubstate in filemerge or
1891 # subrepoutil.submerge yet so we have to keep prompting for it.
1893 # subrepoutil.submerge yet so we have to keep prompting for it.
1892 if b'.hgsubstate' in mresult.actions:
1894 if b'.hgsubstate' in mresult.actions:
1893 f = b'.hgsubstate'
1895 f = b'.hgsubstate'
1894 m, args, msg = mresult.actions[f]
1896 m, args, msg = mresult.actions[f]
1895 prompts = filemerge.partextras(labels)
1897 prompts = filemerge.partextras(labels)
1896 prompts[b'f'] = f
1898 prompts[b'f'] = f
1897 if m == mergestatemod.ACTION_CHANGED_DELETED:
1899 if m == mergestatemod.ACTION_CHANGED_DELETED:
1898 if repo.ui.promptchoice(
1900 if repo.ui.promptchoice(
1899 _(
1901 _(
1900 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1902 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1901 b"use (c)hanged version or (d)elete?"
1903 b"use (c)hanged version or (d)elete?"
1902 b"$$ &Changed $$ &Delete"
1904 b"$$ &Changed $$ &Delete"
1903 )
1905 )
1904 % prompts,
1906 % prompts,
1905 0,
1907 0,
1906 ):
1908 ):
1907 mresult.addfile(
1909 mresult.addfile(
1908 f, mergestatemod.ACTION_REMOVE, None, b'prompt delete',
1910 f, mergestatemod.ACTION_REMOVE, None, b'prompt delete',
1909 )
1911 )
1910 elif f in p1:
1912 elif f in p1:
1911 mresult.addfile(
1913 mresult.addfile(
1912 f,
1914 f,
1913 mergestatemod.ACTION_ADD_MODIFIED,
1915 mergestatemod.ACTION_ADD_MODIFIED,
1914 None,
1916 None,
1915 b'prompt keep',
1917 b'prompt keep',
1916 )
1918 )
1917 else:
1919 else:
1918 mresult.addfile(
1920 mresult.addfile(
1919 f, mergestatemod.ACTION_ADD, None, b'prompt keep',
1921 f, mergestatemod.ACTION_ADD, None, b'prompt keep',
1920 )
1922 )
1921 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1923 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1922 f1, f2, fa, move, anc = args
1924 f1, f2, fa, move, anc = args
1923 flags = p2[f2].flags()
1925 flags = p2[f2].flags()
1924 if (
1926 if (
1925 repo.ui.promptchoice(
1927 repo.ui.promptchoice(
1926 _(
1928 _(
1927 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1929 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1928 b"use (c)hanged version or leave (d)eleted?"
1930 b"use (c)hanged version or leave (d)eleted?"
1929 b"$$ &Changed $$ &Deleted"
1931 b"$$ &Changed $$ &Deleted"
1930 )
1932 )
1931 % prompts,
1933 % prompts,
1932 0,
1934 0,
1933 )
1935 )
1934 == 0
1936 == 0
1935 ):
1937 ):
1936 mresult.addfile(
1938 mresult.addfile(
1937 f,
1939 f,
1938 mergestatemod.ACTION_GET,
1940 mergestatemod.ACTION_GET,
1939 (flags, False),
1941 (flags, False),
1940 b'prompt recreating',
1942 b'prompt recreating',
1941 )
1943 )
1942 else:
1944 else:
1943 mresult.removefile(f)
1945 mresult.removefile(f)
1944
1946
1945 # Convert to dictionary-of-lists format
1947 # Convert to dictionary-of-lists format
1946 actions = mresult.actionsdict
1948 actions = mresult.actionsdict
1947
1949
1948 if not util.fscasesensitive(repo.path):
1950 if not util.fscasesensitive(repo.path):
1949 # check collision between files only in p2 for clean update
1951 # check collision between files only in p2 for clean update
1950 if not branchmerge and (
1952 if not branchmerge and (
1951 force or not wc.dirty(missing=True, branch=False)
1953 force or not wc.dirty(missing=True, branch=False)
1952 ):
1954 ):
1953 _checkcollision(repo, p2.manifest(), None)
1955 _checkcollision(repo, p2.manifest(), None)
1954 else:
1956 else:
1955 _checkcollision(repo, wc.manifest(), actions)
1957 _checkcollision(repo, wc.manifest(), actions)
1956
1958
1957 # divergent renames
1959 # divergent renames
1958 for f, fl in sorted(pycompat.iteritems(mresult.diverge)):
1960 for f, fl in sorted(pycompat.iteritems(mresult.diverge)):
1959 repo.ui.warn(
1961 repo.ui.warn(
1960 _(
1962 _(
1961 b"note: possible conflict - %s was renamed "
1963 b"note: possible conflict - %s was renamed "
1962 b"multiple times to:\n"
1964 b"multiple times to:\n"
1963 )
1965 )
1964 % f
1966 % f
1965 )
1967 )
1966 for nf in sorted(fl):
1968 for nf in sorted(fl):
1967 repo.ui.warn(b" %s\n" % nf)
1969 repo.ui.warn(b" %s\n" % nf)
1968
1970
1969 # rename and delete
1971 # rename and delete
1970 for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)):
1972 for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)):
1971 repo.ui.warn(
1973 repo.ui.warn(
1972 _(
1974 _(
1973 b"note: possible conflict - %s was deleted "
1975 b"note: possible conflict - %s was deleted "
1974 b"and renamed to:\n"
1976 b"and renamed to:\n"
1975 )
1977 )
1976 % f
1978 % f
1977 )
1979 )
1978 for nf in sorted(fl):
1980 for nf in sorted(fl):
1979 repo.ui.warn(b" %s\n" % nf)
1981 repo.ui.warn(b" %s\n" % nf)
1980
1982
1981 ### apply phase
1983 ### apply phase
1982 if not branchmerge: # just jump to the new rev
1984 if not branchmerge: # just jump to the new rev
1983 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1985 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1984 # If we're doing a partial update, we need to skip updating
1986 # If we're doing a partial update, we need to skip updating
1985 # the dirstate.
1987 # the dirstate.
1986 always = matcher is None or matcher.always()
1988 always = matcher is None or matcher.always()
1987 updatedirstate = updatedirstate and always and not wc.isinmemory()
1989 updatedirstate = updatedirstate and always and not wc.isinmemory()
1988 if updatedirstate:
1990 if updatedirstate:
1989 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
1991 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
1990 # note that we're in the middle of an update
1992 # note that we're in the middle of an update
1991 repo.vfs.write(b'updatestate', p2.hex())
1993 repo.vfs.write(b'updatestate', p2.hex())
1992
1994
1993 _advertisefsmonitor(
1995 _advertisefsmonitor(
1994 repo, len(actions[mergestatemod.ACTION_GET]), p1.node()
1996 repo, len(actions[mergestatemod.ACTION_GET]), p1.node()
1995 )
1997 )
1996
1998
1997 wantfiledata = updatedirstate and not branchmerge
1999 wantfiledata = updatedirstate and not branchmerge
1998 stats, getfiledata = applyupdates(
2000 stats, getfiledata = applyupdates(
1999 repo,
2001 repo,
2000 actions,
2002 actions,
2001 wc,
2003 wc,
2002 p2,
2004 p2,
2003 overwrite,
2005 overwrite,
2004 wantfiledata,
2006 wantfiledata,
2005 labels=labels,
2007 labels=labels,
2006 commitinfo=mresult.commitinfo,
2008 commitinfo=mresult.commitinfo,
2007 )
2009 )
2008
2010
2009 if updatedirstate:
2011 if updatedirstate:
2010 with repo.dirstate.parentchange():
2012 with repo.dirstate.parentchange():
2011 repo.setparents(fp1, fp2)
2013 repo.setparents(fp1, fp2)
2012 mergestatemod.recordupdates(
2014 mergestatemod.recordupdates(
2013 repo, actions, branchmerge, getfiledata
2015 repo, actions, branchmerge, getfiledata
2014 )
2016 )
2015 # update completed, clear state
2017 # update completed, clear state
2016 util.unlink(repo.vfs.join(b'updatestate'))
2018 util.unlink(repo.vfs.join(b'updatestate'))
2017
2019
2018 if not branchmerge:
2020 if not branchmerge:
2019 repo.dirstate.setbranch(p2.branch())
2021 repo.dirstate.setbranch(p2.branch())
2020
2022
2021 # If we're updating to a location, clean up any stale temporary includes
2023 # If we're updating to a location, clean up any stale temporary includes
2022 # (ex: this happens during hg rebase --abort).
2024 # (ex: this happens during hg rebase --abort).
2023 if not branchmerge:
2025 if not branchmerge:
2024 sparse.prunetemporaryincludes(repo)
2026 sparse.prunetemporaryincludes(repo)
2025
2027
2026 if updatedirstate:
2028 if updatedirstate:
2027 repo.hook(
2029 repo.hook(
2028 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2030 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2029 )
2031 )
2030 return stats
2032 return stats
2031
2033
2032
2034
2033 def merge(ctx, labels=None, force=False, wc=None):
2035 def merge(ctx, labels=None, force=False, wc=None):
2034 """Merge another topological branch into the working copy.
2036 """Merge another topological branch into the working copy.
2035
2037
2036 force = whether the merge was run with 'merge --force' (deprecated)
2038 force = whether the merge was run with 'merge --force' (deprecated)
2037 """
2039 """
2038
2040
2039 return update(
2041 return update(
2040 ctx.repo(),
2042 ctx.repo(),
2041 ctx.rev(),
2043 ctx.rev(),
2042 labels=labels,
2044 labels=labels,
2043 branchmerge=True,
2045 branchmerge=True,
2044 force=force,
2046 force=force,
2045 mergeforce=force,
2047 mergeforce=force,
2046 wc=wc,
2048 wc=wc,
2047 )
2049 )
2048
2050
2049
2051
2050 def clean_update(ctx, wc=None):
2052 def clean_update(ctx, wc=None):
2051 """Do a clean update to the given commit.
2053 """Do a clean update to the given commit.
2052
2054
2053 This involves updating to the commit and discarding any changes in the
2055 This involves updating to the commit and discarding any changes in the
2054 working copy.
2056 working copy.
2055 """
2057 """
2056 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2058 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
2057
2059
2058
2060
2059 def revert_to(ctx, matcher=None, wc=None):
2061 def revert_to(ctx, matcher=None, wc=None):
2060 """Revert the working copy to the given commit.
2062 """Revert the working copy to the given commit.
2061
2063
2062 The working copy will keep its current parent(s) but its content will
2064 The working copy will keep its current parent(s) but its content will
2063 be the same as in the given commit.
2065 be the same as in the given commit.
2064 """
2066 """
2065
2067
2066 return update(
2068 return update(
2067 ctx.repo(),
2069 ctx.repo(),
2068 ctx.rev(),
2070 ctx.rev(),
2069 branchmerge=False,
2071 branchmerge=False,
2070 force=True,
2072 force=True,
2071 updatedirstate=False,
2073 updatedirstate=False,
2072 matcher=matcher,
2074 matcher=matcher,
2073 wc=wc,
2075 wc=wc,
2074 )
2076 )
2075
2077
2076
2078
2077 def graft(
2079 def graft(
2078 repo,
2080 repo,
2079 ctx,
2081 ctx,
2080 base=None,
2082 base=None,
2081 labels=None,
2083 labels=None,
2082 keepparent=False,
2084 keepparent=False,
2083 keepconflictparent=False,
2085 keepconflictparent=False,
2084 wctx=None,
2086 wctx=None,
2085 ):
2087 ):
2086 """Do a graft-like merge.
2088 """Do a graft-like merge.
2087
2089
2088 This is a merge where the merge ancestor is chosen such that one
2090 This is a merge where the merge ancestor is chosen such that one
2089 or more changesets are grafted onto the current changeset. In
2091 or more changesets are grafted onto the current changeset. In
2090 addition to the merge, this fixes up the dirstate to include only
2092 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
2093 a single parent (if keepparent is False) and tries to duplicate any
2092 renames/copies appropriately.
2094 renames/copies appropriately.
2093
2095
2094 ctx - changeset to rebase
2096 ctx - changeset to rebase
2095 base - merge base, or ctx.p1() if not specified
2097 base - merge base, or ctx.p1() if not specified
2096 labels - merge labels eg ['local', 'graft']
2098 labels - merge labels eg ['local', 'graft']
2097 keepparent - keep second parent if any
2099 keepparent - keep second parent if any
2098 keepconflictparent - if unresolved, keep parent used for the merge
2100 keepconflictparent - if unresolved, keep parent used for the merge
2099
2101
2100 """
2102 """
2101 # If we're grafting a descendant onto an ancestor, be sure to pass
2103 # 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
2104 # 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
2105 # 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
2106 # 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
2107 # newer than the destination so it doesn't prompt about "remote changed foo
2106 # which local deleted".
2108 # which local deleted".
2107 # We also pass mergeancestor=True when base is the same revision as p1. 2)
2109 # 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.
2110 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
2109 wctx = wctx or repo[None]
2111 wctx = wctx or repo[None]
2110 pctx = wctx.p1()
2112 pctx = wctx.p1()
2111 base = base or ctx.p1()
2113 base = base or ctx.p1()
2112 mergeancestor = (
2114 mergeancestor = (
2113 repo.changelog.isancestor(pctx.node(), ctx.node())
2115 repo.changelog.isancestor(pctx.node(), ctx.node())
2114 or pctx.rev() == base.rev()
2116 or pctx.rev() == base.rev()
2115 )
2117 )
2116
2118
2117 stats = update(
2119 stats = update(
2118 repo,
2120 repo,
2119 ctx.node(),
2121 ctx.node(),
2120 True,
2122 True,
2121 True,
2123 True,
2122 base.node(),
2124 base.node(),
2123 mergeancestor=mergeancestor,
2125 mergeancestor=mergeancestor,
2124 labels=labels,
2126 labels=labels,
2125 wc=wctx,
2127 wc=wctx,
2126 )
2128 )
2127
2129
2128 if keepconflictparent and stats.unresolvedcount:
2130 if keepconflictparent and stats.unresolvedcount:
2129 pother = ctx.node()
2131 pother = ctx.node()
2130 else:
2132 else:
2131 pother = nullid
2133 pother = nullid
2132 parents = ctx.parents()
2134 parents = ctx.parents()
2133 if keepparent and len(parents) == 2 and base in parents:
2135 if keepparent and len(parents) == 2 and base in parents:
2134 parents.remove(base)
2136 parents.remove(base)
2135 pother = parents[0].node()
2137 pother = parents[0].node()
2136 # Never set both parents equal to each other
2138 # Never set both parents equal to each other
2137 if pother == pctx.node():
2139 if pother == pctx.node():
2138 pother = nullid
2140 pother = nullid
2139
2141
2140 if wctx.isinmemory():
2142 if wctx.isinmemory():
2141 wctx.setparents(pctx.node(), pother)
2143 wctx.setparents(pctx.node(), pother)
2142 # fix up dirstate for copies and renames
2144 # fix up dirstate for copies and renames
2143 copies.graftcopies(wctx, ctx, base)
2145 copies.graftcopies(wctx, ctx, base)
2144 else:
2146 else:
2145 with repo.dirstate.parentchange():
2147 with repo.dirstate.parentchange():
2146 repo.setparents(pctx.node(), pother)
2148 repo.setparents(pctx.node(), pother)
2147 repo.dirstate.write(repo.currenttransaction())
2149 repo.dirstate.write(repo.currenttransaction())
2148 # fix up dirstate for copies and renames
2150 # fix up dirstate for copies and renames
2149 copies.graftcopies(wctx, ctx, base)
2151 copies.graftcopies(wctx, ctx, base)
2150 return stats
2152 return stats
2151
2153
2152
2154
2153 def purge(
2155 def purge(
2154 repo,
2156 repo,
2155 matcher,
2157 matcher,
2156 unknown=True,
2158 unknown=True,
2157 ignored=False,
2159 ignored=False,
2158 removeemptydirs=True,
2160 removeemptydirs=True,
2159 removefiles=True,
2161 removefiles=True,
2160 abortonerror=False,
2162 abortonerror=False,
2161 noop=False,
2163 noop=False,
2162 ):
2164 ):
2163 """Purge the working directory of untracked files.
2165 """Purge the working directory of untracked files.
2164
2166
2165 ``matcher`` is a matcher configured to scan the working directory -
2167 ``matcher`` is a matcher configured to scan the working directory -
2166 potentially a subset.
2168 potentially a subset.
2167
2169
2168 ``unknown`` controls whether unknown files should be purged.
2170 ``unknown`` controls whether unknown files should be purged.
2169
2171
2170 ``ignored`` controls whether ignored files should be purged.
2172 ``ignored`` controls whether ignored files should be purged.
2171
2173
2172 ``removeemptydirs`` controls whether empty directories should be removed.
2174 ``removeemptydirs`` controls whether empty directories should be removed.
2173
2175
2174 ``removefiles`` controls whether files are removed.
2176 ``removefiles`` controls whether files are removed.
2175
2177
2176 ``abortonerror`` causes an exception to be raised if an error occurs
2178 ``abortonerror`` causes an exception to be raised if an error occurs
2177 deleting a file or directory.
2179 deleting a file or directory.
2178
2180
2179 ``noop`` controls whether to actually remove files. If not defined, actions
2181 ``noop`` controls whether to actually remove files. If not defined, actions
2180 will be taken.
2182 will be taken.
2181
2183
2182 Returns an iterable of relative paths in the working directory that were
2184 Returns an iterable of relative paths in the working directory that were
2183 or would be removed.
2185 or would be removed.
2184 """
2186 """
2185
2187
2186 def remove(removefn, path):
2188 def remove(removefn, path):
2187 try:
2189 try:
2188 removefn(path)
2190 removefn(path)
2189 except OSError:
2191 except OSError:
2190 m = _(b'%s cannot be removed') % path
2192 m = _(b'%s cannot be removed') % path
2191 if abortonerror:
2193 if abortonerror:
2192 raise error.Abort(m)
2194 raise error.Abort(m)
2193 else:
2195 else:
2194 repo.ui.warn(_(b'warning: %s\n') % m)
2196 repo.ui.warn(_(b'warning: %s\n') % m)
2195
2197
2196 # There's no API to copy a matcher. So mutate the passed matcher and
2198 # There's no API to copy a matcher. So mutate the passed matcher and
2197 # restore it when we're done.
2199 # restore it when we're done.
2198 oldtraversedir = matcher.traversedir
2200 oldtraversedir = matcher.traversedir
2199
2201
2200 res = []
2202 res = []
2201
2203
2202 try:
2204 try:
2203 if removeemptydirs:
2205 if removeemptydirs:
2204 directories = []
2206 directories = []
2205 matcher.traversedir = directories.append
2207 matcher.traversedir = directories.append
2206
2208
2207 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2209 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2208
2210
2209 if removefiles:
2211 if removefiles:
2210 for f in sorted(status.unknown + status.ignored):
2212 for f in sorted(status.unknown + status.ignored):
2211 if not noop:
2213 if not noop:
2212 repo.ui.note(_(b'removing file %s\n') % f)
2214 repo.ui.note(_(b'removing file %s\n') % f)
2213 remove(repo.wvfs.unlink, f)
2215 remove(repo.wvfs.unlink, f)
2214 res.append(f)
2216 res.append(f)
2215
2217
2216 if removeemptydirs:
2218 if removeemptydirs:
2217 for f in sorted(directories, reverse=True):
2219 for f in sorted(directories, reverse=True):
2218 if matcher(f) and not repo.wvfs.listdir(f):
2220 if matcher(f) and not repo.wvfs.listdir(f):
2219 if not noop:
2221 if not noop:
2220 repo.ui.note(_(b'removing directory %s\n') % f)
2222 repo.ui.note(_(b'removing directory %s\n') % f)
2221 remove(repo.wvfs.rmdir, f)
2223 remove(repo.wvfs.rmdir, f)
2222 res.append(f)
2224 res.append(f)
2223
2225
2224 return res
2226 return res
2225
2227
2226 finally:
2228 finally:
2227 matcher.traversedir = oldtraversedir
2229 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now