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