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