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