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