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