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