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