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