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