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