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