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