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