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